Flutter 之Router 页面跳转
页面跳转在移动开发中是很常见的事情,在Android中打开另外一个页面主要是用startActivity这个方法,在Flutter中也是提供这种能力,主要的使用方式就是通过Navigator 去打开一个页面
1.跳转到另外一个页面
构建FirstScreen和SecondScreen 页面
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondScreen();
}));
},
child: Text("next screen"),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("back"),
),
),
);
}
}
这里就是跳转的主要代码
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondScreen();
}));
push方式详解
static Future push(BuildContext context, Route route)
第一个参数就是上下问信息,类似Android中的Context,第二个参数就是路由信息,也就是要打开的主要页面是哪个,MaterialPageRoute 就是Route其中的一个子类,用于在Material Desgin 模式下打开页面的
Navigator.pop(context);
是用来返回上一个页面的
2.通过routes路径方式跳转到下一个页面
先定义Routes路由表,实际上就是一个Map结构,key是路径,value就是对应的页面
import 'package:flutter/material.dart';
import 'navigation/navigation_demo.dart';
void main() {
runApp(MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
));
}
routes 就是一个map结构,根目录/对应的页面就是FirstScreen,/second路径对应的页面就是ScendScreen,在FirstScreen中打开SecondScreen的方式我们换一下,要通过Navigator.pushNamed方式打开一个在路由表中已经存在的页面
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/second");
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return SecondScreen();
// }));
},
child: Text("next screen"),
),
),
);
}
}
3.传递数据到下一个页面
传递数据到下一个页面也是比较常见的情况,例如说在一个相册应用中,有一个列表页面,单击列表中某一个item,应该跳转到照片的详情页面,其实这种情况就应该把照片的信息传递给另外一个页面
传递的方式有两种:
- 在构造方法中传递数据
- 在Route中传递数据给下一个页面
在第一个页面构造要传递的数据
class Photo {
String title;
String message;
Photo({this.title, this.message});
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/second", arguments: Photo(title: "pass title",message: "pass message"));
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return SecondScreen();
// }));
},
child: Text("next screen"),
),
),
);
}
}
在第二个页面获取数据
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Photo photo=ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text("Second Screen ${photo.title}"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("back ${photo.message}"),
),
),
);
}
}
这种方式有种不太好的地方就是需要在下一个页面通过ModalRoute.of(context).settings.arguments; 方式获取传递的数据,其实Flutter中已经提供了这种方式简便处理方式
import 'package:flutter/material.dart';
import 'navigation/navigation_demo.dart';
void main() {
runApp(MaterialApp(
home: FirstScreen(),
onGenerateRoute: (settings) {
if (settings.name == ThreeScreen.routeName) {
final Photo args = settings.arguments;
return MaterialPageRoute(builder: (context) {
return ThreeScreen(
title: args.title,
message: args.message,
);
});
}
},
));
}
onGenerateRoute 是用来统一拦截传递参数的方法,我们可以在这个地方获取传递的数据,然后在构造页面的时候把参数传递给目标页面,这样在目标页面也就是不用考虑如何解析传递过来的数据了
class ThreeScreen extends StatelessWidget {
static const routeName = '/extractArguments';
final String title;
final String message;
ThreeScreen({this.title, this.message});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("second"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title),
Text(message),
],
),
),
);
}
}
在这个页面,数据都是通过构造方法中传递了,减少了在页面获取传递数据的代码
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, ThreeScreen.routeName,
arguments: Photo(title: "args title", message: "args message"));
},
child: Text("next screen"),
),
),
);
}
}
发送方式的代码没有改变
4.接收页面返回值
有的时候我们希望在前一个页面接收另外一个页面的数据,这个怎么处理呢
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: FirstButton(),
);
}
}
class FirstButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/second",
arguments:
Photo(title: "pass title", message: "pass message"))
.then((vale) {
final snackBar = SnackBar(
content: Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
});
},
child: Text("next screen"),
),
);
}
}
关键的代码是这段,then 方法用来处理接收数据后的处理逻辑,这个例子中主要通过SnackBar 展示一下接收的信息
Navigator.pushNamed(context, "/second",
arguments:
Photo(title: "pass title", message: "pass message"))
.then((vale) {
final snackBar = SnackBar(
content: Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
});
为什么要单独抽取出FirstButton组件?
是因为SnackBar只能在Scaffold 组件代码中使用会报错
下面代码是用于在推出当前页面的时候,处理了ok 给前一个页面
Navigator.pop(context, "ok");
总结
使用上跟Android 的使用方式类似,有点经验的人掌握这个不是很难
https://docs.flutter.io/flutter/widgets/Navigator-class.html
https://www.raywenderlich.com/110-flutter-navigation-tutorial