本文原作者: Nayuta,原文发布于: 进击的 Flutter
导语
某天在公众号看到这样一个问题
一、初识路由: 一种页面切换的能力
为什么先分析路由,因为问题发生在页面切换的场景下。
二、如何实现一个丐版路由
class RouteHostState extends State<RouteHost> with TickerProviderStateMixin {
List<Widget> pages = []; //路由中的多个页面
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand, //每个页面撑满屏幕
children: pages,
);
}
}
//1、创建一个位移动画
AnimationController animationController;
animation;
animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 500));
animation = Tween(begin: Offset(1, 0), end: Offset.zero)
.animate(animationController);
//2、添加到 stack 中并显示
pages.add(SlideTransition(
position: animation,
child: page,
));
//3、调用 setState 并开启转场动画
setState(() {
animationController.forward();
}
//关闭最后一个页面
void close() async {
//出场动画
await controllers.last.reverse();
//移除最后一个页面
pages.removeLast();
controllers.removeLast().dispose();
}
}
abstract class BuildContext {
///查找父节点中的T类型的State
T findAncestorStateOfType<T extends State>();
///查找父节点中的T类型的 InheritedWidget 例如 MediaQuery 等
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect })
///遍历子元素的element对象
void visitChildElements(ElementVisitor visitor);
......
}
///RouteHost提供给子节点访问自己 State 的能力
static RouteHostState of(BuildContext context) {
return context.findAncestorStateOfType<RouteHostState>();
}
///子节点借助上面的方法使用路由
void openPage() {
RouteHost.of(context).open(RoutePage());
}
https://github.com/Nayuta403/flutter_lifecycle_example
三、理解路由源码设计
有了上面的思考,那么对于源码的设计我们就很清晰了。现在我们回过头来看看路由的使用
Navigator.of(context).push(MaterialPageRoute(builder: (c) {
return PageB();
}));
RouteHost.of(context).open(RoutePage());
static NavigatorState of(BuildContext context) {
///获取位于根部的 Navigator
final NavigatorState navigator = rootNavigator
? context.findRootAncestorStateOfType<NavigatorState>()
: context.findAncestorStateOfType<NavigatorState>();
return navigator;
}
四、源码中的亿点点细节
有了整体大框架之后,我们可以具体梳理 Navigator.of(context).push 过程。
Future<T> push<T extends Object>(Route<T> route) {
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
/// 1、新页面的路由进行添加
route._navigator = this;
route.install(_currentOverlayEntry); ///关键方法!!!!!!!
_history.add(route);
route.didPush();
route.didChangeNext(null);
/// 2、上一个路由的相关回调
if (oldRoute != null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
/// 3、回调 Navigator 的观察者
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
_afterNavigation(route);
return route.popped;
}
route.install(_currentOverlayEntry);
在 OverlayRoute 中如下:
void install(OverlayEntry insertionPoint) {
/// 通过 createOverlayEntries() 创建新页面的 _overlayEntries 集合
/// 这个 _overlayEntries 集合就是我们打开的新页面
_overlayEntries.addAll(createOverlayEntries());
/// 将新页面的 _overlayEntries 集合插入到 overlay 中显示
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
super.install(insertionPoint);
}
Iterable<OverlayEntry> createOverlayEntries() sync* {
/// 创建一个遮罩
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
/// 创建页面实际内容,最终调用到 Route 的 builder 方法
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
/// overlay.dart
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
五、总结
Navigator 作为路由容器内部嵌套了 Stack 提供了页面切换的能力;
通过 context.findRootAncestorStateOfType
Route 为我们封装了切换时需要的其他能力。
当我们切换页面的时候,上一个页面默认会走以下几个生命周期:
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"