Day13 - Flutter - 路由导航
概述
- 路由管理
- 路由基本使用
- 命名路由使用(重点)
- 页面跳转的拓展
一、路由管理
-
1.1、认识
Flutter路由
路由的概念由来已久,包括网络路由、后端路由,到现在广为流行的前端路由。- 无论路由的概念如何应用,它的
核心是一个路由映射表
- 比如:名字
detail
映射到DetailPage
页面等 - 有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发(在前端表现出来的就是页面跳转)
在Flutter中,路由管理主要有两个类:
Route
和Navigator
- 无论路由的概念如何应用,它的
-
1.2、Route
Route:一个页面要想被路由统一管理,必须包装为一个Route- 官方的说法很清晰:An abstraction for an entry managed by a Navigator.
但是Route是一个抽象类,所以它是不能实例化的
-
在上面有一段注释,让我们查看MaterialPageRoute来使用
/// See [MaterialPageRoute] for a route that replaces the /// entire screen with a platform-adaptive transition. abstractclass Route<T> { }
事实上MaterialPageRoute并不是Route的直接子类:
- MaterialPageRoute在不同的平台有不同的表现
- 对
Android平台
,打开一个页面会从屏幕底部滑动到屏幕的顶部,关闭页面时从顶部滑动到底部消失 - 对
iOS平台
,打开一个页面会从屏幕右侧滑动到屏幕的左侧,关闭页面时从左侧滑动到右侧消失 - 当然,iOS平台我们也可以使用CupertinoPageRoute
- 继承关系:
MaterialPageRoute
->PageRoute
->ModalRoute
->TransitionRoute
->OverlayRoute
->Route
-
1.3、Navigator
Navigator:管理所有的Route的Widget,通过一个Stack来进行管理的-
官方的说法也很清晰:A widget that manages a set of child widgets with a stack discipline.
那么我们开发中需要手动去创建一个Navigator吗? -
并不需要,我们开发中使用的MaterialApp、CupertinoApp、WidgetsApp它们默认是有插入Navigator的
-
所以,我们在需要的时候,只需要直接使用即可
Navigator.of(context)
Navigator有几个最常见的方法:
// 路由跳转:传入一个路由对象 Future<T> push<T extendsObject>(Route<T> route) // 路由跳转:传入一个名称(命名路由) Future<T> pushNamed<T extendsObject>( String routeName, { Object arguments, }) // 路由返回:可以传入一个参数 bool pop<T extendsObject>([ T result ])
-
二、路由基本使用
-
2.1、基本跳转
我们来实现一个最基本跳转:
创建首页页面,中间添加一个按钮,点击按钮跳转到详情页面
创建详情页面,中间添加一个按钮,点击按钮返回到首页页面
跳转自己的详情页-
核心的跳转代码如下
// 跳转的按钮 RaisedButton( child: Text('跳转 书影集 详情'), onPressed: () { print('跳转 书影集 详情'); _jumpToHomeDtail(context); }, ), // 按钮点击后的跳转 void _jumpToHomeDtail(BuildContext context) { Navigator.of(context).push(MaterialPageRoute( builder: (ctx) { return JKSubjectDetailPage(); } )); }
提示:JKSubjectDetailPage 是一个书影集的详情页面,代码如下
import 'package:flutter/material.dart'; class JKSubjectDetailPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('书影集详情'), ), body: JKSubjectDetailContent(), ); } } class JKSubjectDetailContent extends StatefulWidget { @override _JKSubjectDetailContentState createState() => _JKSubjectDetailContentState(); } class _JKSubjectDetailContentState extends State<JKSubjectDetailContent> { @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("书影集详情"), RaisedButton( child: Text('返回上个界面'), onPressed: () { print('返回上个界面'); _popLastPage(context); }, ), ], ), ); } // 返回上一个界面 void _popLastPage(BuildContext context) { Navigator.of(context).pop(); } }
返回的拓展:
- 1、自定义返回:
Navigator.of(context).pop('这里可以加参数,参数的类型是泛型');
- 2、监听返回按钮的点击(给Scaffold包裹一个
WillPopScope
)-
WillPopScope有一个onWillPop的回调函数,当我们点击返回按钮时会执行
-
这个函数要求有一个Future的返回值:
true
:那么系统会自动帮我们执行pop操作;false
:系统不再执行pop操作,需要我们自己来执行WillPopScope( onWillPop: () { Navigator.of(context).pop("a back detail message"); return Future.value(false); }, child: Scaffold( appBar: AppBar( title: Text('书影集详情'), ), body: JKSubjectDetailContent(_detailmesaage), ), );
-
- 1、自定义返回:
-
-
2.2、参数传递,下面我们以传递一个字符串为例
在跳转过程中,我们通常可能会携带一些参数,比如- 书影集首页 跳到详情页,携带一条信息:a home message
- 详情页返回首页,携带一条信息:a detail message
我们需要在 详情页 定义一个属性 String,那么我们在类JKSubjectDetailPage
里面定义为String _detailmesaage = '';
- 首页跳转核心代码:
-
在页面跳转时,会返回一个Future
-
该Future会在详情页面调用pop时,回调对应的then函数,并且会携带结果
// 跳转的代码 void _jumpToHomeDtail(BuildContext context) { Future result = Navigator.of(context).push(MaterialPageRoute( builder: (ctx) { // 普通的跳转直接通过构造器传递即可 return JKSubjectDetailPage('我是书影音首页传过去的数据'); } )); result.then((value) { print('返回的结果是:$value'); }); } // 返回的核心代码 void _popLastPage(BuildContext context) { // pop<T extends Object>([ T result ]) 返回的结果是一个泛型 Navigator.of(context).pop('我是返回的结果'); }
-
三、命名路由使用
-
3.1. 基本跳转
我们可以通过创建一个新的Route,使用Navigator来导航到一个新的页面,但是如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码。-
在这种情况下,我们可以使用
命名路由(named route)
- 命名路由是将名字和路由的映射关系,在一个地方进行统一的管理
- 有了命名路由,我们可以通过Navigator.pushNamed() 方法来跳转到新的页面
-
命名路由在哪里管理呢?可以放在MaterialApp的 initialRoute 和 routes 中
-
initialRoute
:设置应用程序从哪一个路由开始启动,设置了该属性,就不需要再设置home属性了 -
routes
:定义名称和路由之间的映射关系,类型为Map<String, WidgetBuilder>
-
-
修改MaterialApp中的代码:
return MaterialApp( title: 'FlutterDemo', theme: ThemeData( primaryColor: Colors.blue, splashColor: Colors.transparent, // 点击的水波纹设置为无色 highlightColor: Colors.transparent ), // 启动要显示的界面 // home: JKMainPage(), initialRoute: JKRouter.initialRoute, // 路由列表 routes: JKRouter.routes, // 当routes里面找不到的时候,就会到这里根据名字找到对应的页面 onGenerateRoute: JKRouter.generateRoute, // 路由占不到的错误界面 onUnknownRoute: JKRouter.unknownRoute, );
我们定义一个
routers.dart
文件,class JKRouter { // 路由数组 static final Map<String, WidgetBuilder> routes = { JKMainPage.routeName: (ctx) => JKMainPage(), JKHomePage.routeName: (ctx) => JKHomePage(), JKSubjectDetailPage.routeName: (ctx) => JKSubjectDetailPage(), }; // 首先展示的界面 static final String initialRoute = JKMainPage.routeName; // 当routes里面找不到的时候,就会到这里根据名字找到对应的页面 static final RouteFactory generateRoute = (settings) { if (settings.name == JKSubjectDetailPage.routeName) { return MaterialPageRoute( builder: (ctx) { return JKSubjectDetailPage(); } ); } return null; }; // 根据路由找不到界面的时候做展示的错误页面 static final RouteFactory unknownRoute = (settings) { return MaterialPageRoute( builder: (ctx) { return JKUnknownPage(); } ); }; }
修改跳转的代码:
Future result = Navigator.of(context).pushNamed(JKSubjectDetailPage.routeName, arguments: '我是参数'); result.then((value) { setState(() { _message = value; }); print('返回的结果是:$value'); });
在开发中,为了让每个页面对应的routeName统一,我们通常会在每个页面中定义一个路由的常量来使用:下面以 书影音的详情页为例
class JKSubjectDetailPage extends StatefulWidget { static const String routeName = "/subjectdetail"; }
以后有新的页面我们就修改
router.dart
文件 和新的页面增加 routeName(如上面的代码)// 路由数组 static final Map<String, WidgetBuilder> routes = { JKMainPage.routeName: (ctx) => JKMainPage(), JKHomePage.routeName: (ctx) => JKHomePage(), JKSubjectDetailPage.routeName: (ctx) => JKSubjectDetailPage(), };
-
-
3.2、参数传递
因为通常命名路由,我们会在定义路由时,直接创建好对象,比如JKSubjectDetailPage()
那么,命名路由如果有参数需要传递呢?
pushNamed时,如何传递参数:_onPushTap(BuildContext context) { Navigator.of(context).pushNamed(JKSubjectDetailPage.routeName, arguments: "a home message of naned route"); }
在JKSubjectDetailPage中,如何获取到参数呢?在build方法中ModalRoute.of(context)可以获取到传递的参数
Widget build(BuildContext context) { // 1.获取数据 final message = ModalRoute.of(context).settings.arguments; }
四、页面跳转的拓展
-
4.1、模态跳转和push跳转
void _jumpToHomeDtail(BuildContext context) { Navigator.of(context).push(MaterialPageRoute( builder: (ctx) { return JKProfileDetail(); }, fullscreenDialog: false )); }
提示: fullscreenDialog: false
- true: 从底部显示下个页面,iOS里面叫 模态跳转
- false: 从右边推出显示下个页面,iOS里面叫 push 跳转
-
4.2、PageRouteBuilder 渐变跳转
void _jumpToHomeDtail(BuildContext context) { Navigator.of(context).push(PageRouteBuilder( // 渐变所需要的时间 transitionDuration: Duration(seconds: 3), pageBuilder: (ctx, animarion1, animation2) { return FadeTransition( // 透明度 opacity: animarion1, child: JKProfileDetail(), ); } )); }