Flutter 2 种导航(路由)方式介绍

不浪费大家时间阅读全文,还是提到开始,帮助大家抓重点阅读。

  1. push和pushNamed无参数导航
  2. push和pushNamed含参数导航
  3. 404路由自定义
  4. git 克隆指定分支和切换分支

正文

路由的概念很广泛,其主要功能是约定一套到达目的的映射规则,方便使用。本场景中是定义一个字符串映射到某个界面。

Flutter 有两种路由形式实现导航,

  • Navigator.of(context).pushNamed(routeName, arguments),
  • Navigator.of(context).push(route);

及其衍生的形式,常见的如导航到某个页面后要删除导航路径中的某些页面。如果是导航到无参数的页面,两种形式实现都很直接;如果是有参数的页面,使用 pushNamed 会稍微绕一点,下面说明下两种方式的使用方式。

Starter 启动工程

开始之前,先创建2个界面,分别是

  • WithParameterPage,接收参数的页面
  • WithoutParameterPage,不接收参数的页面

加上首页,一共3个界面,可通过 git 下载到本地

git clone https://github.com/juwencheng/flutter_route_demo.git -b starter

导航到无参页面

push方式

push 方法接收的参数是 Route<T> 类型的对象,通常我们使用系统提供的 MaterialPageRoute,它提供跨平台的导航动画效果,安卓是从下往上的模态动画,苹果是从右往左的水平切换动画,下面是官网对 MaterialPageRoute 的介绍

A modal route that replaces the entire screen with a platform-adaptive transition.
For Android, the entrance transition for the page slides the page upwards and fades it in. The exit transition is the same, but in reverse.
The transition is adaptive to the platform and on iOS, the page slides in from the right and exits in reverse. The page also shifts to the left in parallax when another page enters to cover it. (These directions are flipped in environments with a right-to-left reading direction.)
By default, when a modal route is replaced by another, the previous route remains in memory. To free all the resources when this is not necessary, set maintainState to false.
The fullscreenDialog property specifies whether the incoming page is a fullscreen modal dialog. On iOS, those pages animate from the bottom to the top rather than horizontally.
The type T specifies the return type of the route which can be supplied as the route is popped from the stack via Navigator.pop by providing the optional result argument.

https://docs.flutter.io/flutter/material/MaterialPageRoute-class.html

实现代码如下

Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WithoutParameterPage(),
),
);

pushNamed 方式

pushNamed 方法接收两个参数,其中 String 类型的 routeName 为必需,Object 类型命名参数 arguments 为可选参数,下面节选自官网

Push a named route onto the navigator that most tightly encloses the given context.

https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html

代码实现如下

Navigator.of(context).pushNamed(
'without_parameter_page',
);

可以看到形式上简洁很多。不过还没完,需要在 main.dart 的 MaterialApp 对象中添加 routes 对象定义 String 到 页面的映射规则,代码如下

...
routes: {
'/without_parameter_page': (context) => WithoutParameterPage(),
},
...

单纯的字符串并不容易记忆,一旦拼写错误,就无法导航到预期的界面,因此通常会在 StatefulWidget 或 StatelessWidget 中定义静态属性 routeName

class WithoutParameterPage extends StatefulWidget {
static constString routeName = "/without_parameter_page";
@override
_WithoutParameterPageState createState() => _WithoutParameterPageState();
}

修改 main.dart 中的定义

routes: {
WithoutParameterPage.routeName: (context) => WithoutParameterPage(),
},

及使用

Navigator.of(context).pushNamed(
WithoutParameterPage.routeName,
);

总结起来使用 pushNamed 方式比 push 方式多一个定义的过程。

完整代码可切换分支到 without_parameter 查看

git checkout without_parameter

如果没有下载,可通过如下命令直接克隆 without_parameter 分支

git clone https://github.com/juwencheng/flutter_route_demo.git -b without_parameter

导航到含参页面

看完上面的介绍,兴致勃勃的你已经迫不及待想自己尝试下如何导航到含参页面了。过程大致是

  • 打开 WithParameterPage,发现已经定义了 parameters,直接使用
  • 使用 push 方式导航时,把 WithoutParameterPage 修改为 WithParameterPage,并传入参数 {“hello”: “world”},运行,啪,导航过去了,并在界面中央出现了 {“hello”: “world”},完美。
  • 使用 pushNamed方式导航时,
    • 在 WithParameterPage 中定义 routeName
    • 在 main.dart 中定义路由规则 WithParameterPage.routeName: (context) => WithParameterPage()
    • 调用 pushNamed 方法时,第二个参数用来接收参数的,继续将 {“hello”: “world”} 传入,运行,准备见证奇迹的时候,发现界面中并未接收到参数,是不是有缓存,再运行一次,还是不行。

本以为可以顺利实现,结果却令人疑惑。回到方法本身,思考个问题,传递的参数,是怎么绑定到界面的呢?文档中没有提及反射,自动绑定或者约定一种命名方式让程序可以处理。再阅读官方文档

The route name will be passed to that navigator’s onGenerateRoute callback. The returned route will be pushed into the navigator.
The new route and the previous route (if any) are notified (see Route.didPush and Route.didChangeNext). If the Navigator has any Navigator.observers, they will be notified as well (seeNavigatorObserver.didPush).
Ongoing gestures within the current route are canceled when a new route is pushed.
Returns a Future that completes to the result value passed to pop when the pushed route is popped off the navigator.
The T type argument is the type of the return value of the route. The provided arguments are passed to the pushed route via RouteSettings.arguments. Any object can be passed as arguments (e.g. a Stringint, or an instance of a custom MyRouteArguments class). Often, a Map is used to pass key-value pairs.
The arguments may be used in Navigator.onGenerateRoute or Navigator.onUnknownRoute to construct the route.

https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html

末尾发现了一点端倪,提刀了 onGenerateRoute 方法。就在 MaterialApp 试试有没有此方法,正好发现方法,着手改动一番。(如果没那么巧,1. 顺着文档继续挖下去,看能够找到切入点;2. 谷歌查询关键字,搜索别人的使用方法)

onGenerateRoute: (settings) {
print(settings);
return MaterialPageRoute(builder: (context) => WithParameterPage());
},

在 MaterialApp 中添加以上代码后,运行,期待打印出 settings 信息,尝试多次后发现并没有。有点小失望?继续往下看

直接公布答案,在 routes 中定义 ‘/with_parameter_page’ 规则后,系统可以自动处理该路由,因此不会进入 onGenerateRoute,需将 routes 中的定义删除

routes: {
WithoutParameterPage.routeName: (context) => WithoutParameterPage(),
// WithParameterPage.routeName: (context) => WithParameterPage(),
},

接下来完善 onGenerateRoute 代码

switch (settings.name) {
case WithParameterPage.routeName:
return MaterialPageRoute(
builder: (context) =>
WithParameterPage(parameters: settings.arguments));
default:
break;
}

至此,两种路由方式介绍完毕。最后还想提一下 onUnknownRoute 方法,在无法确保路由写错的情况下,可以通过 onUnknownRoute 指定一个默认的 404 界面来暴露问题,方便查找(如果不暴露问题,界面会吞掉错误,不会有响应,需查看控制台发现问题)。代码如下

onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(settings: settings),
);
},

NotFoundPage 的内容可切换到 with_parameter 分支查看,切换方法见上小节。

文中代码格式化效果不佳,正想办法解决,如果有好的建议可提供。

如果遇到无法运行或者通过文中代码无法达到预期效果等问题,欢迎在github上提交issue。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注