前言
一个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!');
},
onPressed: () {
Navigator.pop(context, 'Nope.');
},
如果在耗时操作前面添加await 字段,则在此处阻塞,等耗时操作返回后继续往下运行。
第一个页面的跳转按钮回调如下:跳转以后第一个页面就阻塞阻塞了,等从第二个页面返回后取到result 值,然后继续执行下面的代码。
onPressed: () {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
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 {
const DetailScreen({Key? key, required this.todo}) : super(key: key);
final Todo todo;
@override
Widget build(BuildContext context) {
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',
),
);
|