Navigator.of(context).pushNamed
用flutter开发界面最离不开的就是路由器,你只要需要跳转到其他界面就需要路由功能,而flutter的ui组成全部都是widget,在布局的时候我们没有用路由的小部件(Navigator),那么它是怎么起到作用的呢,肯定是隐藏在我们用的根部件里面,一般我们布局的根部件为MaterialApp小部件,MaterialApp小部件的build方法构建了WidgetsApp小部件
Widget build(BuildContext context) {
Widget result = WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) {
return MaterialPageRoute(settings: settings, builder: builder);
},
.....省略若干行
}
而WidgetsApp小部件的build的方法会构建Navigator路由小部件,要显示的内容小部件都会成为Navigator的孩子部件,例如home制定小部件会成为Navigator部件的孩子部件,而我们要跳转的操作一般都会用
Navigator.push
Navigator.of(context).pushNamed
等。
Widget build(BuildContext context) {
Widget navigator;
if (_navigator != null) {
navigator = Navigator(
key: _navigator,
// If window.defaultRouteName isn't '/', we should assume it was set
// intentionally via `setInitialRoute`, and should override whatever
// is in [widget.initialRoute].
initialRoute: WidgetsBinding.instance.window.defaultRouteName != Navigator.defaultRouteName
? WidgetsBinding.instance.window.defaultRouteName
: widget.initialRoute ?? WidgetsBinding.instance.window.defaultRouteName,
onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute,
observers: widget.navigatorObservers,
);
}
.....省略若干行
}
而Navigator小部件的的build方法则最终由Overlay来显示路由的界面
Widget build(BuildContext context) {
assert(!_debugLocked);
assert(_history.isNotEmpty);
return Listener(
onPointerDown: _handlePointerDown,
onPointerUp: _handlePointerUpOrCancel,
onPointerCancel: _handlePointerUpOrCancel,
child: AbsorbPointer(
absorbing: false, // it's mutated directly by _cancelActivePointers above
child: FocusScope(
node: focusScopeNode,
autofocus: true,
child: Overlay(
key: _overlayKey,
initialEntries: _initialOverlayEntries,
),
),
),
);
}
而Overlay小部件又通过路由器存储了多少个界面将界面以_OverlayEntry小部件的形式加入到onstageChildren(需要绘制的路由) 集合中,而offstageChildren 是不需要绘制的小部件集合。
Widget build(BuildContext context) {
// These lists are filled backwards. For the offstage children that
// does not matter since they aren't rendered, but for the onstage
// children we reverse the list below before adding it to the tree.
final List onstageChildren = [];
final List offstageChildren = [];
bool onstage = true;
for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i];
if (onstage) {
onstageChildren.add(_OverlayEntry(entry));
//如果不透明的话,下面的布局不用显示了
if (entry.opaque)
onstage = false;
} else if (entry.maintainState) {
//不需要渲染的路由小部件
offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
}
}
return _Theatre(
onstage: Stack(
fit: StackFit.expand,
children: onstageChildren.reversed.toList(growable: false),
),
offstage: offstageChildren,
);
}
通俗的说就是你的MaterialApp的home属性显示的就是当前的路由界面,如果你需要跳转到路由B界面的话,B界面会显示在home界面的上面,也就是说把home界面当做路由A,路由B界面会显示在A界面上面,所以A界面就被B界面覆盖了,从而起到了跳转的作用,这是因为路由A界面和路由B界面的父部件是Stack(和帧布局类似)。
接下来来看一下默认的第一个路由界面的获取,这个获取是在Navigator的State的initState方法里面实现的
void initState() {
super.initState();
for (NavigatorObserver observer in widget.observers) {
assert(observer.navigator == null);
observer._navigator = this;
}
String initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName;
if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
initialRouteName = initialRouteName.substring(1); // strip leading '/'
assert(Navigator.defaultRouteName == '/');
final List> plannedInitialRoutes = >[
_routeNamed(Navigator.defaultRouteName, allowNull: true, arguments: null),
];
final List routeParts = initialRouteName.split('/');
if (initialRouteName.isNotEmpty) {
String routeName = '';
for (String part in routeParts) {
routeName += '/$part';
plannedInitialRoutes.add(_routeNamed(routeName, allowNull: true, arguments: null));
}
}
if (plannedInitialRoutes.last == null) {
push(_routeNamed
widget.initialRoute,initialRoute属性大家应该比较熟吧,就是设置一个字符串,好让路由管理器找到第一个需要渲染的路由界面
,defaultRouteName默认值为“/”,而push方法就是向_history加入不同的路由界面,最终将所有的路由界面放_initialOverlayEntries中交给Overlay最终显示出来。
而_routeNamed方法是用来获得路由界面的,代码如下
Route _routeNamed(String name, { @required Object arguments, bool allowNull = false }) {
final RouteSettings settings = RouteSettings(
name: name,
isInitialRoute: _history.isEmpty,
arguments: arguments,
);
//调用onGenerateRoute获得路由器界面
Route route = widget.onGenerateRoute(settings);
if (route == null && !allowNull) {
//获得不到就调用onUnknownRoute获得
route = widget.onUnknownRoute(settings);
}
return route;
}
onGenerateRoute和onUnknownRoute就是我们配置的MaterialApp的两个方法属性
MaterialApp(
onUnknownRoute: Router.generateRoute ,
title: 'Flutter Unit',
debugShowCheckedModeBanner: false,
onGenerateRoute: Router.generateRoute,
theme: ThemeData(
primarySwatch: state.themeColor,
fontFamily: state.fontFamily,
),
home: UnitSplash()),
)
在调用onGenerateRoute之前先调用的_onGenerateRoute,如下所示
Route _onGenerateRoute(RouteSettings settings) {
final String name = settings.name;
final WidgetBuilder pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
? (BuildContext context) => widget.home
: widget.routes[name];
if (pageContentBuilder != null) {
final Route route = widget.pageRouteBuilder(
settings,
pageContentBuilder,
);
return route;
}
if (widget.onGenerateRoute != null)
return widget.onGenerateRoute(settings);
return null;
}
如果home属性不为null的话就会给把home作为路由的首界面,如果为null就会调用onGenerateRoute方法决定首个路由界面是哪一个。
再来看一下Navigator.of(context).pushNamed进入新的界面的方法,路由管理类的真正实现都是在NavigatorState中,
Future push(Route route) {
final Route oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
//真正实现界面的更新
route.install(_currentOverlayEntry);
//将新的路由界面加入到历史集合中
_history.add(route);
//调用新的route的生命周期
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
//调用老的route的生命周期
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
_afterNavigation(route);
return route.popped;
}
其实Route类就是一个包装类,一是用来处理生命周期方法的,二是用来build小部件返回给Overlay最终显示在Stack小部件上,继续看引起重新渲染的route.install方法,install是在OverlayRoute中实现的
void install(OverlayEntry insertionPoint) {
assert(_overlayEntries.isEmpty);
_overlayEntries.addAll(createOverlayEntries());
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
super.install(insertionPoint);
}
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
其中 navigator就是我们的路由管理器,而insertAll就是向Overlay小部件的实现类OverlayState
void insertAll(Iterable entries, { OverlayEntry below, OverlayEntry above }) {
if (entries.isEmpty)
return;
for (OverlayEntry entry in entries) {
assert(entry._overlay == null);
entry._overlay = this;
}
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
也就是说最终显示的小部件navigator导航器的Overlay小部件,调用setState方法引起Overlay下的孩子重新渲染,它的孩子就包括我们的路由界面。这里还要注意一下createOverlayEntries方法
Iterable createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
返回了两个OverlayEntry,第一个为遮罩层,第二个为我们要定义的路由界面,遮罩常用于对话框,其实flutter
的对话框就是走的路由的原理,还有通过Overlay来创造提示框或悬浮框也是走的这个路由的原理,或者说路由的原理就是Overlay小部件通过Stack来实现的。