在 Flutter 里实现页面的导航需要使用两个类:Navigator和 Route。Navigator 负责页面的栈结构处理,Route 复杂页面的路径处理。
在 Flutter 里导航器使用栈的结构管理页面,当需要添加一个页面时,使用入栈的方式。当需要退出一个页面时,使用出栈的方式。也可以不入栈而是直接替换当前页面。
路由表
移动应用程序通常管理大量的路由,通过名称来引用它们通常是最容易的。
简单的做法是在 runApp 里,就定义好所有的路由,这样可以做集中式管理,也是非常建议的做法。
一个 MaterialApp 是最简单的设置方式,MaterialApp 的 home 成为导航器堆栈底部的路由。要在堆栈上推送(push)一个新路由,可以创建一个具有构建器功能的MaterialPageRoute 实例。
void main() {
runApp(new MaterialApp(
home: new MyAppHome(),
// 路由
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => new MyPage(title: 'page A'),
'/b': (BuildContext context) => new MyPage(title: 'page B'),
'/c': (BuildContext context) => new MyPage(title: 'page C'),
},
));
}
当需要路由切换的时候,可以这样处理。
Navigator.pushNamed(context, '/b');
传递数据
很多时候在切换路由时需要传递一些数据,比如 id 之类的,那么应该怎么做?
在使用动态路由时,可以通过闭包环境来传递数据。
String id = 'abc'; // <--- id
Navigator.push(context, new MaterialPageRoute<void> (
// 新的页面
builder: (BuildContext context) {
return new Scaffold(
body: new Center(
child: new FlatButton(
child: new Text('POP'),
onPressed: () {
print(this.id); // <--- id
Navigator.pop(context);
},
),
),
);
},
));
在使用路由表的时候,只能提供全局变量来传递信息。
var id = 'abc';
void main() {
runApp(new MaterialApp(
home: new MyAppHome(),
// 路由
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => new MyPage(id: id),
},
));
}
入栈一个页面
当需要入栈一个页面时,使用 Navigator.push
函数,这个函数接受一个上下文和新的页面结构。第二个参数是一个路由器 MaterialPageRoute
。
// 在某个点击事件里
Navigator.push(context, new MaterialPageRoute<void> (
// 新的页面
builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('My Page')),
body: new Center(
child: new FlatButton(
child: new Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));
出栈一个页面
当需要出栈一个页面时,使用 Navigator.pop
函数,这个函数接受一个上下文。
new FlatButton(
child: new Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
接受返回值
实际上 pushNamed
是一个异步函数,它返回一个 Future
,在使用 Navigator.pop
的时候,第二个参数就是返回的值。
Navigator.pop(context, 'abc');
Navigator.pushNamed(context, '/router/second').then((value) {
print(value); // --> abc
});
导航切换动画
默认的导航切换动画,在 Android 上是一个从底端到顶端的弹出过程,在 iOS 上是一个从右边到左边的平移过程。
为了统一,可以通过自定义一个路由,包括路由模态屏障的颜色和行为以及路由其他方面的动画转换。
// 动画效果,要把它放在外面去,并且是 static 的。
static SlideTransition createTransition(Animation<double> animation, Widget child) {
return new SlideTransition(
// 从右到左
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: child,
);
}
Navigator.of(context).push(
new PageRouteBuilder(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// 目标页
return new PageView();
},
transitionsBuilder: ( // --> 固定写法
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) => HomePageState.SlideTransition(animation, child),
));
实际上,默认在 Android 上的效果是:
// 从下到上
position: new Tween<Offset>(
begin: const Offset(0.0, 1.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
这里只是把 x 与 y 对换就变成了平移。
得出几个不同的方向动画。
// 从上到下
position: new Tween<Offset>(
begin: const Offset(0.0, -1.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
// 从左到右
position: new Tween<Offset>(
begin: const Offset(-1.0, 0.0), // (x, y)
end: const Offset(0.0, 0.0),
).animate(animation),
神奇的是 begin 可以使用对角移动的方式,也就是 (1.0, 1.0)、(-1.0, -1.0)
。
除了平移之外,还可以定义一些旋转的动画。
static FadeTransition createFadeTransition(Animation<double> animation, Widget child) {
return new FadeTransition(
opacity: animation,
child: new RotationTransition(
turns: new Tween<double>(
begin: 0.8,
end: 1.0
).animate(animation),
child: child,
),
);
}
当 begin 与 end 都为 1.0 时,FadeTransition 是一个透明渐变过程。