一个APP往往是由很多个页面组成的,单独的一个页面在安卓里面称为Activity
,IOS称为ViewController
,在Flutter里面仅仅是一个Widget
。本文讲解Flutter的路由,Flutter
内的路由组件有Navigator
和Router
。简单的可以用Navigator
,更复杂的可以用Router
。主要学习两个页面之间的跳转和传参,以及跨屏动画。
在Flutter中,Navigator
维护了一个堆栈,用来管理页面路由。可以通过Navigator.push()
和Navigator.pop()
来压栈和出栈。
在第一个页面添加一个按钮,回调函数如下:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
在第二个页面SecondRoute
添加一个返回按钮,回调如下:
onPressed: () {
Navigator.pop(context);
}
相对于上面的简单路由,命名路由可以让你在APP多个地方跳转同一个页面时避免代码重复。可以向下面一样创健路由表:
void main() {
runApp(
MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => const FirstScreen(),
'/second': (context) => const SecondScreen(),
},
),
);
}
在第一个页面添加一个按钮,回调函数如下:
onPressed: () {
Navigator.pushNamed(context, '/second');
}
在第二个页面SecondScreen
添加一个返回按钮,回调如下:
onPressed: () {
Navigator.pop(context);
}
根据解析参数的主体不同,可以分为两种方式。一种是由ExtractArgumentsScreen
组件根据传过来的arguments
自己解析出参数;第二种是在MaterialApp
下提供的onGenerateRoute
函数里进行解析,完成以后作为构造函数的参数传给跳转目标组件,这样目标组件不用做任何特殊处理。
arguments
参数,arguments
允许你传任何类型的对象,所以可以自定义一个参数对象使用。 onPressed: () {
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
},
参数对象类型如下,包含两个String
类型的标题和信息。
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
arguments
,并且转换成ScreenArguments
类型。final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
onGenerateRoute()
函数解析arguments
参数。 onPressed: () {
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'Accept Arguments Screen',
'This message is extracted in the onGenerateRoute '
'function.',
),
);
},
arguments
参数并且转换成ScreenArguments
类型。 onGenerateRoute: (settings) {
if (settings.name == PassArgumentsScreen.routeName) {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
assert(false, 'Need to implement ${settings.name}');
return null;
},
有时候第一个页面需要知道用户进入第二个页面以后操作了什么,这种情形就需要第二个页面返回一个值,告诉第一个页面:用户在我这里做了什么。下面的例子展示了如何让第一个页面知道用户在第二个页面点击了Yep还是Nope。
第二个页面加两个按钮,回调分别如下:
//第一个:
onPressed: () {
Navigator.pop(context, 'Yep!');//带着参数Yep返回上一个页面
},
//第二个
onPressed: () {
Navigator.pop(context, 'Nope.');
},
如果在耗时操作前面添加
await
字段,则在此处阻塞,等耗时操作返回后继续往下运行。
第一个页面的跳转按钮回调如下:跳转以后第一个页面就阻塞阻塞了,等从第二个页面返回后取到result
值,然后继续执行下面的代码。
onPressed: () {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
//以下代码级联写法,先移除旧的SnackBar(),再显示新的SnackBar()
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('$result')));
}
},
我们之前讲过,再Flutter
中,每一个页面就是一个组件,假设我要从商品List
页面,跳转到Detail
页面,Detail
页面需要知道用户是通过点击哪个商品id
跳转过来的,方便展示详情信息。List
在跳转时可以把id
作为Detail
的构造函数参数传给Detail
.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
Detail
页面只需要在构造函数添加一个参数,从此参数里面取值,无需特殊处理。
class DetailScreen extends StatelessWidget {
// In the constructor, require a Todo.
const DetailScreen({
Key? key, required this.todo}) : super(key: key);
// Declare a field that holds the Todo.
final Todo todo;
@override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
且看一下这个动效:
这是两个页面,但是这个跨屏动态效果将两个页面的两个窗口关联在一起,可以引导用户的关注点。
这个特效使用了一个组件如下:组件会自动关联
Hero(
tag: 'imageHero',
child: Image.network(
'https://image.haier.com/cn/cooling/W020210723365974095796_60.png',
),
);