本文原作者: 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类型的StateT 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) {///获取位于根部的 Navigatorfinal 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.dartvoid 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"