一.什么是flutter的路由
路由是用于页面跳转的一种方式,方便管理页面之间的跳转和互相传递数据,进行交互。使用路由,我们可以轻松实现从一个页面转换到另一个页面,系统底层其实是在帮我们将小部件执行入栈出栈操作。
在Android中,我们开启一个新页面是Activity。在iOS中,我们开启一个新页面是ViewControllers。而在Flutter中,每一个页面都是小部件, 在Flutter中我们是使用了Navigator的Api实现了页面的跳转与交互。
Navigator的Api在flutter的1.22的版本中在之前的基础上进行了Navigator 2.0的更新,引入了一套全新的声明式 API来实现路由的交互。
二.flutter路由的原理
用flutter开发界面最离不开的就是路由器,只要是涉及界面跳转就需要路由功能,而flutter的ui组成全部都是widget,在布局的时候我们并没有直接使用到路由部件(Navigator),那么它是在什么时候起作用的呢,通过翻看源码可以发现是从布局的根部件MaterialApp开始一步一步引入的。
一般app的根部件为MaterialApp,而它的build方法构建了WidgetsApp部件。
Widget build(BuildContext context) {
Widget result = _buildWidgetApp(context);
...
return ScrollConfiguration(
behavior: _MaterialScrollBehavior(),
child: HeroControllerScope(
controller: _heroController,
child: result,
)
);
}
Widget _buildWidgetApp(BuildContext context) {
...
return WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: widget.navigatorObservers!,
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
return MaterialPageRoute<T>(settings: settings, builder: builder);
},
...
);
}
复制代码
WidgetsApp部件的build的方法会构建Navigator路由部件,要显示的内容部件都会成为Navigator的孩子部件,我们要跳转的操作一般都会用Navigator.push等方法。
Widget build(BuildContext context) {
Widget? routing;
if (_usesRouter) {
...
} else if (_usesNavigator) {
assert(_navigator != null);
routing = Navigator(
restorationScopeId: 'nav',
key: _navigator,
initialRoute: _initialRouteName,
...
);
}
}
复制代码
而Navigator部件的的build方法则最终由Overlay来显示路由的界面。
Widget build(BuildContext context) {
...
return HeroControllerScope.none(
child: Listener(
...
child: Overlay(
key: _overlayKey,
initialEntries: _initialOverlayEntries,
),
),
);
}
复制代码
而Overlay部件又通过路由器存储了多少个界面将界面以_OverlayEntry部件的形式加入到onstageChildren(需要绘制的路由) 集合中,而offstageChildren 是不需要绘制的部件集合。
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[];
bool onstage = true;
int onstageCount = 0;
for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i];
if (onstage) {
onstageCount += 1;
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
));
if (entry.opaque)
onstage = false;
} else if (entry.maintainState) {
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
tickerEnabled: false,
));
}
}
/// Special version of a [Stack], that doesn't layout and render the first [skipCount] children.
/// The first [skipCount] children are considered "offstage".
return _Theatre(
skipCount: children.length - onstageCount,
children: children.reversed.toList(growable: false),
clipBehavior: widget.clipBehavior,
);
}
复制代码
也就是说你的MaterialApp的home属性显示的就是当前的路由界面,如果你需要跳转到路由B界面的话,B界面会显示在home界面的上面,也就是说把home界面当做路由A,路由B界面会显示在A界面上面,所以A界面就被B界面覆盖了,从而起到了跳转的作用,这是因为路由A界面和路由B界面的父部件是Stack。
默认的第一个路由界面的获取,是在Navigator的defaultGenerateInitialRoutes方法里面实现的。
const Navigator({
...
this.initialRoute,
this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
...
}
//initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName;
static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
final List<Route<dynamic>?> result = <Route<dynamic>?>[];
if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
initialRouteName = initialRouteName.substring(1); // strip leading '/'
...
}
...
return result.cast<Route<dynamic>>();
}
//defaultGenerateInitialRoutes返回值赋值给onGenerateInitialRoutes
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
...
if (initialRoute != null) {
_history.addAll(
widget.onGenerateInitialRoutes(
this,
widget.initialRoute ?? Navigator.defaultRouteName,
)
);
}
}
}
复制代码
widget.initialRoute,就是我们自定义设置的启动页面,让路由管理器找到第一个需要渲染的路由界面,defaultRouteName默认值为“/”,而navigator的push方法就是向内部的_history列表加入不同的路由界面,最终将所有的路由界面放_initialOverlayEntries中交给Overlay最终显示出来。
综上所述路由的原理是Overlay部件通过Stack来实现的。
三.navigator1.0的使用说明
在 Flutter 中路由用法主要有两种用法:一种是在 MaterialApp 里的 routes 参数里配置定义好路由列表,也就是提前定义好要跳转的页面和名称,然后通过这个名称来执行跳转。另一种则是通过onGenerateRoute来设置路由跳转规则实现页面跳转交互,并可通过navigatorObservers实现路由的监听。
Route 在 Flutter 中主要有两种实现方法:一个是使用 MaterialPageRoute;另一个是使用 PageRouteBuilder 来构建。
MaterialPageRoute({
// 构建页面
@required this.builder,
// 路由设置
RouteSettings settings,
// 是否保存页面状态、内容
this.maintainState = true,
bool fullscreenDialog = false,
})
复制代码
PageRouteBuilder({
// 路由设置
RouteSettings settings,
// 目标页面
@required this.pageBuilder,
// 跳转过度动画设置
this.transitionsBuilder = _defaultTransitionsBuilder,
this.transitionDuration = const Duration(milliseconds: 300),
...
})
复制代码
使用PageRouteBuilder 构建不仅可以实现路由的基本配置还可以设置跳转动画效果。
页面跳转传递参数,不直接使用在 MaterialApp 的 routes 属性里静态定义的属性值而是使用页面参数配置的动态路由方法,并通过then或者await取得参数。
// 关闭页面返回数据
Navigator.pop(context, '返回数据');
// 接收返回数据
Navigator.push<String>(context,
MaterialPageRoute(builder: (BuildContext context) {
return ButtonSamples(title: '标题', name: '名称');
})).then((String result) {
//处理代码
});
复制代码
四.Navigator2.0的简述
2.0路由的升级,更多地是为了满足 Web 端复杂路由的需要,同时也是满足状态驱动界面设计的理念。即界面与行为进行分离,通过更改状态来驱动界面完成既定行为。因此,2.0路由最关键的地方就是之前的 Navigator.push或Navigator.pop方法的使用变更,界面是通过响应用户操作去更改数据状态,页面路由跳转统一交给了 RougterDelegater来完成。