前言
最近开发的某个软件需要用到嵌套路由,一开始的实现方法是在 MaterialApp 的页面中再包一个 MaterialApp,这样嵌套起来好像没什么问题。
但是在我测试的时候,发现两个问题:
- 在子路由中用系统的返回键返回,这时候子路由和父路由都会跟着返回了。
- 子路由有a和b两个页面,两个页面中的某个 Widget 都有 Hero 动画,从a跳到b页面的时候,b页面会马上再跳转到a页面。
其中问题1很容易解决,使用 WillPopScope 包着 MaterialApp,监听返回事件进行处理就行了。但是问题2却没有找到解决方法,再加上这样可能会遇到其他问题,所以我放弃了这样嵌套。
解决方法
既然嵌套 MaterialApp 不行,那要怎么实现呢?
查了下源码,找到了MaterialApp 的 onGenerateRoute 参数,该参数接收一个Function,可以实现拦截命名路由。
我们可以在 onGenerateRoute中,获取到当前要跳转的页面的路由名字,根据名字判断是不是子路由的页面,如果是子路由页面,把该页面传进父页面里面返回。
所以有了以下代码:
import 'package:flutter/material.dart';
typedef Widget NestedRouteBuilder(Widget child);
class NestedRoute {
final Map<String, NestedRoute> subRoutes;
final NestedRouteBuilder builder;
const NestedRoute({this.subRoutes, @required this.builder});
Route buildRoute(List<String> paths, int index) {
return MaterialPageRoute(
builder: (_) => _build(paths, index),
);
}
Widget _build(List<String> paths, int index) {
if (index >= paths.length) {
final child = subRoutes != null ? subRoutes['/'] : null;
return builder(child?.builder(null));
}
return builder(subRoutes[paths[index]]?._build(paths, index + 1));
}
}
RouteFactory buildNestedRoutes(Map<String, NestedRoute> routes) {
return (RouteSettings settings) {
List<String> paths = [];
settings.name.split('/').forEach((element) {
if(element != '') paths.add(element);
});
if(paths.length == 0){
return routes['/']?.buildRoute([], 1);
}
return routes[paths[0]].buildRoute(paths, 1);
};
}
解释一下,假如我们跳转的路由名称是“/parent/child/son”,从中我们可以知道 parent 里面包着 child ,child 里面包着 son 。在 onGenerateRoute 中,返回 parent(child(son)) 就能实现嵌套的效果了。
使用示例:
@override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateRoute: buildNestedRoutes(
{
'/': NestedRoute(builder: (_) => Text('我是默认页面')),
'parent': NestedRoute(
builder: (child) => Column(
children: [
Text('is parent'),
child,
],
),
subRoutes: {
'child': NestedRoute(
builder: (son) => Column(
children: [
Text('is child'),
son,
],
),
subRoutes: {
'son': NestedRoute(builder: (_) => Text('is son')),
})
),
}),
...
}
),
);
}
Navigator.pushNamed(context, '/parent/child/son');