目标:
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 {@overrideWidget build(BuildContext context) {// 需要返回MaterialApp,MaterialApp内部已经实现了Navigatorreturn 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 {@overrideWidget build(BuildContext context) {// 返回页面为脚手架开始,公用MaterialAppreturn 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 {@overrideWidget build(BuildContext context) {// 需要返回MaterialApp,MaterialApp内部已经实现了Navigatorreturn MaterialApp(home: MainRoute(),);}
}class MainRoute extends StatelessWidget{@overrideWidget 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 {@overrideWidget build(BuildContext context) {// 返回页面为脚手架开始,公用MaterialAppreturn 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 {@overrideWidget build(BuildContext context) {// 需要返回MaterialApp,MaterialApp内部已经实现了Navigatorreturn MaterialApp(home: MainRoute(),routes: {RouteTable.ROUTE_MAIN: (_) {return new MainRoute();},RouteTable.ROUTE_SECOND: (_) {return new SecondRoute();}},);}
}class MainRoute extends StatelessWidget {@overrideWidget 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 {@overrideWidget build(BuildContext context) {// 返回页面为脚手架开始,公用MaterialAppreturn 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);@overrideString toString() {return 'Result{name: $name, score: $score}';}
}class SecondRoute extends StatelessWidget {@overrideWidget build(BuildContext context) {// 返回页面为脚手架开始,公用MaterialAppreturn 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{@overrideWidget 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 {@overrideWidget build(BuildContext context) {// 需要返回MaterialApp,MaterialApp内部已经实现了Navigatorreturn MaterialApp(home: MainRoute(),// routes: {// RouteTable.ROUTE_MAIN: (_) {// return new MainRoute();// },// RouteTable.ROUTE_SECOND: (_) {// return new SecondRoute();// }// },);}
}class MainRoute extends StatelessWidget {@overrideWidget 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 {@overrideWidget build(BuildContext context) {// 返回页面为脚手架开始,公用MaterialAppreturn 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 {@overrideWidget 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("进入第二页"),)],),);}
}