深入进阶-Flutter Navigator详解
无论是IOS还是安卓,都有对应页面堆栈的管理。而在Flutter中,使用Navigator进行页面切换管理。需要注意的是Flutter中页面的概念对应的是Route,并不是Scaffold,Scaffold只是一个Widget,帮助我们搭建页面UI而已。那Navigator有哪些操作,对应原理是什么呢?
Push相关操作
Future push(Route route)
通过Router打开一个新页面
//通过一个列表以栈的形式维护了所有的Router信息
final List<Route<dynamic>> _history = <Route<dynamic>>[];
//根据Router信息开启一个新页面
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
//取_history.last,也就是当前的页面Router
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
//处理页面置顶的Overlay相关内容
route.install(_currentOverlayEntry);
//将当前Router添加进列表中
_history.add(route);
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
//回调给上一个Router信息,内部处理了一些动画状态
oldRoute.didChangeNext(route);
//将旧的Router回调给新的Router
route.didChangePrevious(oldRoute);
}
_afterNavigation(route);
//返回一个Future对象
return route.popped;
}
在Flutter中,我们常用Navigator.of(context).push(Router)
方法开启一个页面。查看源码可知,Flutter通过一个列表List<Route<dynamic>> _history
维护所有的路由信息,整个push过程主要是就是将新的路由添加至这个列表的队尾,并进行相关的回调,最后返回了一个Future对象。这个Future对象,往往用于页面关闭时候的回调。可以理解push页面是一个带返回值的耗时操作,那什么时候这个Future会有返回值呢,肯定是当启动的这个页面pop的时候。果然,pop方法会调用到bool didPop(T result)
方法中,执行这个future的 complete([FutureOr<T> value])
回传返回值。
static Future pushNamed(String routeName, {Object arguments,})
通过routeName打开一个新页面
1、Navigator.dart
//根据路由名称开启一个页面
@optionalTypeArgs
static Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
//最终会调用到上面的push方法,所以这里的_routeNamed会根据routeName返回一个Router
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
2、Navigator.dart
Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
//根据RouteName生成一个setting信息
final RouteSettings settings = RouteSettings(
name: name,
isInitialRoute: _history.isEmpty,
arguments: arguments,
);
//这里的widget是Navigator,应该是Navigator初始化额时候外部传入的
Route<T> route = widget.onGenerateRoute(settings);
if (route == null && !allowNull) {
route = widget.onUnknownRoute(settings);
}
return route;
}
pushNamed<T extends Object>(String routeName, {Object arguments,})
通过routeName打开一个新页面,其实最终都是调用上面的push方法,由(2)可知这里的Router是由widget.onGenerateRoute(settings)
生成的。这里的onGener肯定是在Navigator初始化的时候传入,那Navigator什么时候会初始化或者说Navigator是如何嵌套入app中的?
反向查看调用信息,发现Navigator被嵌套在WidgetsApp中,而WigetsApp在初始化的时候将下面的方法传入了Navigator。
//存储App中路由信息的Map,一般是在runApp()的时候自定义
final Map<String, WidgetBuilder> routes;
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
final String name = settings.name;
final WidgetBuilder pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
? (BuildContext context) => widget.home
//通过路由表查找对应的Router
: widget.routes[name];
if (widget.onGenerateRoute != null)
return widget.onGenerateRoute(settings);
return null;
}
所以pushNamed<T extends Object>(String routeName, {Object arguments,})
方法就是利用我们在MaterialApp中定义好的路由信息生成Router对象,最终调用push方法。后面与Name相关的都类似,就不做额外分析了。
Future pushReplacement<T extends Object, TO extends Object>(Route newRoute, { TO result })
关闭当前页面并且打开一个新的页面
Future<T> pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result }) {
final Route<dynamic> oldRoute = _history.last;
//这里的index就是移除的routerIndex
final int index = _history.length - 1;
newRoute._navigator = this;
newRoute.install(_currentOverlayEntry);
_history[index] = newRoute;
newRoute.didPush().whenCompleteOrCancel(() {
// The old route's exit is not animated. We're assuming that the
// new route completely obscures the old one.
if (mounted) {
//调用上一个页面的关闭与资源释放
oldRoute
..didComplete(result ?? oldRoute.currentResult)
..dispose();
}
});
newRoute.didChangeNext(null);
//与push保持一致的状态回调
if (index > 0) {
_history[index - 1].didChangeNext(newRoute);
newRoute.didChangePrevious(_history[index - 1]);
}
_afterNavigation(newRoute);
return newRoute.popped;
}
通过对比push过程发现,pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result })
先将新的Router添加到集合中。在页面Push完成后,push之前的页面进行清理,这里如果方法参数中传入的result不为空,可以通知到打开上一个页面的页面,之后和push一样,进行相关的状态回调。
Future pushAndRemoveUntil(Route newRoute, RoutePredicate predicate)
通过predicate将之前所有的Router回传给调用方,从当前页面开始,关闭满足predicate的一系列页面,之后打开一个新页面。
@optionalTypeArgs
Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate) {
// The route that is being pushed on top of
final Route<dynamic> precedingRoute = _history.isNotEmpty ? _history.last : null;
// Routes to remove
final List<Route<dynamic>> removedRoutes = <Route<dynamic>>[];
//每次都把栈顶的Router信息回传给外界,如果predicate返回false则将这个Router加入待关闭列表中
while (_history.isNotEmpty && !predicate(_history.last)) {
final Route<dynamic> removedRoute = _history.removeLast();
removedRoutes.add(removedRoute);
}
final Route<dynamic> newPrecedingRoute = _history.isNotEmpty ? _history.last : null;
newRoute._navigator = this;
newRoute.install(precedingRouteOverlay);
_history.add(newRoute);
newRoute.didPush().whenCompleteOrCancel(() {
if (mounted) {
//当新页面启动成功后,关闭上面收集的所有页面
for (Route<dynamic> removedRoute in removedRoutes) {
for (NavigatorObserver observer in widget.observers)
observer.didRemove(removedRoute, newPrecedingRoute);
removedRoute.dispose();
}
if (newPrecedingRoute != null)
newPrecedingRoute.didChangeNext(newRoute);
}
});
newRoute.didChangeNext(null);
for (NavigatorObserver observer in widget.observers)
observer.didPush(newRoute, precedingRoute);
_afterNavigation(newRoute);
return newRoute.popped;
}
这个方法会循环的把栈顶的页面取出,根据predicate判断是否要销毁这个页面,直到当predicate返回ture停止,关闭了从当前页面开始满足条件的多个页面。这个操作类似于安卓设置某个页面为SingleTask的形式
Pop操作
bool pop([ T result ])
@optionalTypeArgs
bool pop<T extends Object>([ T result ]) {
final Route<dynamic> route = _history.last;
bool debugPredictedWouldPop;
//didPop中,将result传给了打开这个页面的地方
if (route.didPop(result ?? route.currentResult)) {
if (_history.length > 1) {
_history.removeLast();
// If route._navigator is null, the route called finalizeRoute from
// didPop, which means the route has already been disposed and doesn't
// need to be added to _poppedRoutes for later disposal.
if (route._navigator != null)
//这里有_poppedRoutes的set集合收集退出的页面
_poppedRoutes.add(route);
//回调通知当前的页面
_history.last.didPopNext(route);
for (NavigatorObserver observer in widget.observers)
observer.didPop(route, _history.last);
} else {
return false;
}
}
_afterNavigation<dynamic>(route);
return true;
}
pop的操作非常直观,将当前栈顶的页面关闭,并且将result作为返回结果回传给启动这个页面的地方。
Remove操作
void removeRoute(Route route)
将指定的Route移除
void removeRoute(Route<dynamic> route) {
final int index = _history.indexOf(route);
final Route<dynamic> previousRoute = index > 0 ? _history[index - 1] : null;
final Route<dynamic> nextRoute = (index + 1 < _history.length) ? _history[index + 1] : null;
_history.removeAt(index);
previousRoute?.didChangeNext(nextRoute);
nextRoute?.didChangePrevious(previousRoute);
for (NavigatorObserver observer in widget.observers)
observer.didRemove(route, previousRoute);
route.dispose();
_afterNavigation<dynamic>(nextRoute);
}
将制定的route从栈中移除,并且通知这个rout前后的页面。