在Flutter
中,页面的跳转是经过Navigator
来实现。经过几句简单的代码就能够实现页面的跳转并传递对应的参数。那么具体实现是怎样的尼?下面就来一窥究竟。数组
在Flutter
中,一切皆Widget
,Navigator
也不能例外。但咱们并无主动添加Navigator
,可是又能够经过Navigator
来进行页面跳转,这是怎么回事尼?缓存
咱们在进行Flutter
开发时,必须以MaterialApp
或WidgetsApp
来做为第一个Widget
,不然就会出错。也就是在WidgetsApp
中(MaterialApp
是对WidgetsApp
的包装),Flutter
默认给咱们添加了第一个Navigator
,通常跳转都是根据这个Navigator
来进行的。这也就是咱们没有主动添加Navigator
,但却可以经过Navigator
来进行页面跳转的缘由。以下图所示。 markdown
Widget
树中的第一个
Navigator
,即根
Navigator
。
对于Navigator
,咱们通常都是经过Navigator.of(context)
来获取NavigatorState
对象,而后经过NavigatorState
对象中的函数来实现页面跳转、关闭、替换等。下面来看Navigator.of(context)
的实现。ide
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
final NavigatorState navigator = rootNavigator
//获取根Navigator
? context.findRootAncestorStateOfType<NavigatorState>()
//获取距离当前Widget最近的Navigator
: context.findAncestorStateOfType<NavigatorState>();
return navigator;
}
复制代码
rootNavigator
为true
表示获取根Navigator
,不然获取距离当前Widget
最近的Navigator
,默认为false
。函数
能够发现,这里要想获取根Navigator
,就须要传递一个context
对象,但在某些需求(如token
失效跳转登陆页)下,没法拿到context
对象,但又须要跳转,那这该怎么办尼?post
这时候咱们能够给根Navigator
传递一个key
,而后根据这个key
拿到NavigatorState
对象。而MaterialApp
又给咱们提供了向根Navigator
传递key
的机会。动画
class MaterialApp extends StatefulWidget {
const MaterialApp({
Key key,
//自定义key
this.navigatorKey,
...
})
}
复制代码
经过给navigatorKey
赋一个GlobalKey
对象,而后经过这个对象就能够不须要context
来得到根Navigator
,从而进行页面的跳转、返回、替换等。ui
当咱们正确的拿到NavigatorState
对象后,就能够经过push
函数来跳转到到新的页面。this
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
//获取上一个route
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
//获取当前OverlayEntry对象
route.install(_currentOverlayEntry);
//将当前route添加到集合中
_history.add(route);
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
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
(路由)是一个很是重要的概念,它对页面进行了抽象。在Flutter
中,一个页面对应一个route
对象。_history
是NavigatorState
中的一个数组,也是一般所说的路由栈。在跳转一个新页面时,会将route
对象添加到_history
数组中,弹出页面则从该数组中移除route
对象。spa
在上面代码中,route.install(_currentOverlayEntry)
是页面跳转的核心实现。install
函数在Route
中是一个空实现,在其子类ModalRoute
中进行了具体实现。
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
......
@override
void install(OverlayEntry insertionPoint) {
super.install(insertionPoint);
_animationProxy = ProxyAnimation(super.animation);
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
}
......
}
复制代码
在ModalRoute
里的install
函数中主要作了动画相关的处理,而后交给父类执行。在其父类TransitionRoute
主要是作动画的建立及相关处理,并交给其父类处理。那么再来看TransitionRoute
的父类OverlayRoute
。
abstract class OverlayRoute<T> extends Route<T> {
.....
/// Subclasses should override this getter to return the builders for the overlay.
Iterable<OverlayEntry> createOverlayEntries();
/// The entries this route has placed in the overlay.
@override
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
@override
void install(OverlayEntry insertionPoint) {
//建立页面对应的两个OverlayEntry对象并加入到_overlayEntries中,以待后续处理。
_overlayEntries.addAll(createOverlayEntries());
//进行UI的更新
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
//空实现
super.install(insertionPoint);
}
......
}
复制代码
在OverlayRoute
中有一个抽象函数createOverlayEntries
,该函数须要在子类实现。代码以下。
@override
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
复制代码
yield
的含义先无论。该函数主要是建立两个OverlayEntry
对象,一个是咱们须要展现页面所对应的OverlayEntry
对象,一个是Barrier所对应的OverlayEntry
对象。它两的关系以下。
route
所对应的两个
OverlayEntry
对象建立成功后,就加入到集合
_overlayEntries
中,而后调用
OverlayState
的
insertAll
函数。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
final List<OverlayEntry> _entries = <OverlayEntry>[];
......
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
if (entries.isEmpty)
return;
for (OverlayEntry entry in entries) {
entry._overlay = this;
}
//更新UI
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
@override
Widget build(BuildContext context) {
final List<Widget> onstageChildren = <Widget>[];
final List<Widget> offstageChildren = <Widget>[];
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,
);
}
......
}
复制代码
在insertAll
里,就能够发现页面跳转的本质了。就是经过setState
来更新UI。
再来看OverlayState
的build
的函数,在该函数中会对须要保存在内存中的页面进行分类,主要有如下两类。
maintainState
为true时就会添加到该集合,目前面跳转的maintainState
参数默认为true。来看一个示例。假设有三个页面page一、page2及page3。咱们先给page2页面的maintainState
属性设置不一样值,而后观察page2跳转到page3时page2页面的变化状况。
能够发现,当maintainState
为true时,会在内存中缓存page2页面的全部对象,以便下次操做。固然若是后面不须要再次展现page2,那么就最好将maintainState
设为false。
前面分析了在flutter
中如何跳转一个新的页面,那么来看一下如何关闭一个页面。关闭页面是调用的pop
函数。
bool pop<T extends Object>([ T result ]) {
//从路由栈中获取当前页面对应的route对象
final Route<dynamic> route = _history.last;
bool debugPredictedWouldPop;
if (route.didPop(result ?? route.currentResult)) {
if (_history.length > 1) {
//从集合中移除当前页面所对应的route对象
_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.add(route);
_history.last.didPopNext(route);
for (NavigatorObserver observer in widget.observers)
observer.didPop(route, _history.last);
RouteNotificationMessages.maybeNotifyRouteChange(_routePoppedMethod, route, _history.last);
} else {
return false;
}
} else {}
_afterNavigation<dynamic>(route);
return true;
}
复制代码
这里重点是didPop
这个函数。该函数中会调用NavigatorState
的finalizeRoute
函数来释放资源。而finalizeRoute
又会调用route
的dispose
函数。
abstract class OverlayRoute<T> extends Route<T> {
@override
bool didPop(T result) {
final bool returnValue = super.didPop(result);
assert(returnValue);
//释放资源操做
if (finishedWhenPopped)
navigator.finalizeRoute(this);
return returnValue;
}
//释放资源
@override
void dispose() {
//从_overlayEntries集合中移除当前页面及Barrier层
for (OverlayEntry entry in _overlayEntries)
entry.remove();
_overlayEntries.clear();
super.dispose();
}
}
复制代码
在dispose
函数中,会将_overlayEntries
集合中清空,这里之因此是集合并遍历,是由于一个页面对应2个OverlayEntry
对象。
void remove() {
final OverlayState overlay = _overlay;
//取消OverlayEntry对OverlayState对象的引用
_overlay = null;
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
//若是当前正在渲染UI,则会等待渲染完毕后,才更新。
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
//更新UI
overlay._remove(this);
});
} else {
//更新UI
overlay._remove(this);
}
}
复制代码
再来看OverlayState
的_remove
函数,该函数也是调用了setState
函数。而后等待UI的刷新便可。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
final List<OverlayEntry> _entries = <OverlayEntry>[];
......
void _remove(OverlayEntry entry) {
if (mounted) {
setState(() {
_entries.remove(entry);
});
}
}
......
}
复制代码
再来看上一个示例,在page2页面关闭时的变化状况。
能够发现,当maintainState
为false时,从page3返回page2,会从新建立page2页面的全部对象。若是page2页面比较复杂,那么此举就比较耗资源,想必这也是maintainState
默认为true的缘由吧。
固然,Navigator
还有其余实现函数,如pushNamed
(根据routeName跳转)、pushAndRemoveUntil
(关闭并跳转到新页面)、replace
(替换当前页面)等。但这些函数的具体实现跟push
及pop
的实现基本上一致,因此只要理解了flutter
中如何进行页面跳转,那么也就基本上了解了Navigator
的的实现原理。也就知道了弹窗的打开、关闭的实现原理,由于这些操做也是经过Navigator
来实现的。
【参考资料】