目标:
1.Flutter怎么创建路由?
2.怎么实现路由跳转?页面返回?
一、路由
1.1 什么是路由?
路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。
路由通常通过维护一个路由表,建立页面导航表。
1.2 路由导航
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
Text("这是第一页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
/// TODO: 导航跳转第二页
debugPrint("导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
Navigator.push(context, MaterialPageRoute(builder: (_) {
return SecondRoute();
}));
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 返回页面为脚手架开始,公用MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("第二页"),
),
body: Column(
children: [
Text("这是第二页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
Navigator.pop(context);
},
// 按钮显示内容(返回上一页)
child: Text("返回"),
)
],
),
);
}
}
新建两个页面,第一个页面点击按钮,跳转第二个页面。
报错信息如下。
1.2.1 导航问题分析
导航操作请求使用了不包含Navigator的上下文context
`Navigator`实际上也是一个Widget,这个异常出现在`Navigator.of(context)`路由器的获取上,而这句代码会**从当前的context的父级一层层向上去查找一个`Navigator`**,我们当前传递的context就是MyApp,它的父级是root——UI根节点。`Navigator`这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得`Navigator`。
在之前所有的路由案例中,我们的上下文是MainRoute,它的父级是MaterialApp。MaterialApp内部就会创建一个Navigator。
MaterialApp->\_MaterialAppState->WidgetsApp->\_WidgetsAppState
所以问题就在于,`Navigator`需要通过MaterialApp或者它孩子的上下文。
1.2.2 导航解决方案
Navigator必须在MaterialApp下一级,这样获取的Element的上下文才是MaterialApp的上下文。
解决方案一:MaterialApp下body提取一级MainRoute
新的层级结构
root
|---MaterialApp-->Navigator
|--------->MainRoute
是指MainRoute的层级在MaterialApp下一级。
这样,MainRoute就能够访问父Element的Navigator。
跳转第二页成功。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
return MaterialApp(
home: MainRoute(),
);
}
}
class MainRoute extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
Text("这是第一页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
/// TODO: 导航跳转第二页
debugPrint("导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
/// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
/// Navigator必须在MaterialApp下一级
///
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondRoute();
}));
// Navigator.push(MaterialPageRoute(
//
// ))
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 返回页面为脚手架开始,公用MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("第二页"),
),
body: Column(
children: [
Text("这是第二页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
Navigator.pop(context);
},
// 按钮显示内容(返回上一页)
child: Text("返回"),
)
],
),
);
}
}
解决方案二:MaterialApp.Builder构建子树
MaterialApp下的子控件Builder,通过Builder构建的子树,上下文是Builder,因此一定在MaterialApp下面。
1.3 命名路由
给页面增加路由名字,建立路由表。
1.3.1 注册路由表
MaterialApp.routes注册路由表。
路由表定义路由名称和对应的路由导航页面。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class RouteTable {
static String ROUTE_MAIN = "/main";
static String ROUTE_SECOND = "/second";
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
return MaterialApp(
home: MainRoute(),
routes: {
RouteTable.ROUTE_MAIN: (_) {
return new MainRoute();
},
RouteTable.ROUTE_SECOND: (_) {
return new SecondRoute();
}
},
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
Text("这是第一页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
/// TODO: 导航跳转第二页
debugPrint("命名路由导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
/// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
/// Navigator必须在MaterialApp下一级
/// 命令路由跳转的时候采用路由表
Navigator.pushNamed(context, RouteTable.ROUTE_SECOND);
// Navigator.push(MaterialPageRoute(
//
// ))
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 返回页面为脚手架开始,公用MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("第二页"),
),
body: Column(
children: [
Text("这是第二页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
Navigator.pop(context);
},
// 按钮显示内容(返回上一页)
child: Text("返回"),
)
],
),
);
}
}
1.3.2 路由导航
路由导航通过命名路由进行导航。
Navigator.pushNamed(context, RouteTable.ROUTE_SECOND);
二、页面参数返回
在项目中,跳转一个新页面以后,处理完成,回到第一个页面,可能需要处理返回来的参数。
这就需要涉及到页面参数返回和接收。
2.1 返回参数保存
Navigator.pop携带返回结果
class Result {
String name;
int score;
Result(this.name, this.score);
@override
String toString() {
return 'Result{name: $name, score: $score}';
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 返回页面为脚手架开始,公用MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("第二页"),
),
body: Column(
children: [
Text("这是第二页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
/// 返回上一个页面,携带处理结果。例如当前处理结果是一个对象
Navigator.pop(context, new Result("超新星", 100));
},
// 按钮显示内容(返回上一页)
child: Text("返回"),
)
],
),
);
}
}
Navigator.pop携带一个结果返回上一页。
2.2 接收返回结果
第一页需要接收页面返回结果
2.2.1 onPress方法修改为异步方法 async
对应异步接收处理的方法,声明为async。
2.2.2 Navigator.push的异步返回结果接收
class MainRoute extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
Text("这是第一页"),
RaisedButton(
/// 1) 修改为异步任务,等待页面返回
onPressed: () async {
/// 实现点击事件
debugPrint("导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
/// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
/// Navigator必须在MaterialApp下一级
/// 2) 通过await等待返回结果
///
Result result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return SecondRoute();
}));
debugPrint("接收结果 result = " + result.toString());
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
);
}
}
返回结果数据,是泛型数据,顶级类Object的子类。因此几乎所有类型都可以。
三、定制页面切换动画
Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder。
3.1 页面水平切换
导航到下一个页面的时候,增加水平滑动效果。
SlideTransition是水平滑动动画,position定义平移的动画效果。
我们采用Tween补间动画效果。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class RouteTable {
/// 首页默认使用 / 定义这个路由的话,MaterialApp的home不需要重复定义
static String ROUTE_MAIN = "/";
static String ROUTE_SECOND = "/second";
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 需要返回MaterialApp,MaterialApp内部已经实现了Navigator
return MaterialApp(
home: MainRoute(),
// routes: {
// RouteTable.ROUTE_MAIN: (_) {
// return new MainRoute();
// },
// RouteTable.ROUTE_SECOND: (_) {
// return new SecondRoute();
// }
// },
);
}
}
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
RaisedButton(
onPressed: () {
/// 实现点击事件
debugPrint("命名路由导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
/// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
/// Navigator必须在MaterialApp下一级
/// push 的时候,增加路由跳转动画效果
Navigator.push(context, PageRouteBuilder(pageBuilder:
(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
/// child导航的第二个页面
child: SecondRoute(),
);
}));
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 返回页面为脚手架开始,公用MaterialApp
return Scaffold(
appBar: AppBar(
title: Text("第二页"),
),
body: Column(
children: [
Text("这是第二页"),
RaisedButton(
onPressed: () {
/// 实现点击事件
Navigator.pop(context);
},
// 按钮显示内容(返回上一页)
child: Text("返回"),
)
],
),
);
}
}
需要注意切换到第二个页面,child为SecondRoute
3.2 渐变+滑动动画
在滑动动画外层嵌套一层渐变动画。
child对应滑动动画。
class MainRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
body: Column(
children: [
RaisedButton(
onPressed: () {
/// 实现点击事件
debugPrint("命名路由导航跳转第二页");
// 定义导航路由(导航到SecondRoute)
/// 因为context是MyApp的BuildContext,MyApp不包含Navigator,因此报错
/// Navigator必须在MaterialApp下一级
/// push 的时候,增加路由跳转动画效果
Navigator.push(
context,
PageRouteBuilder(
/// 动画时长
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation) {
/// 嵌套一层渐变动画
return FadeTransition(
opacity: animation,
/// 渐变动画+滑动动画
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
/// child导航的第二个页面
child: SecondRoute(),
));
}));
},
// 按钮显示内容
child: Text("进入第二页"),
)
],
),
);
}
}