flutter 路由机制的实现

作者:无若叶 时间:2021-11-18 08:28:46 

目录
  • 实现基础

  • _routeNamed

  • _flushHistoryUpdates

    • add

    • push

    • pop

    • remove

  • 总结

    整个 flutter 应用的运行都只是基于原生应用中的一个 view,比如 android 中的 FlutterView,flutter 中的页面切换依赖于它的路由机制,也就是以 Navigator 为中心的一套路由功能,使得它能够完成与原生类似且能够自定义的页面切换效果。

    下面将介绍 flutter 中的路由实现原理,包括初始化时的页面加载、切换页面的底层机制等。

    实现基础

    flutter 应用的运行需要依赖 MaterialApp/CupertinoApp 这两个 Widget,他们分别对应着 android/ios 的设计风格,同时也为应用的运行提供了一些基本的设施,比如与路由相关的主页面、路由表等,再比如跟整体页面展示相关的 theme、locale 等。

    其中与路由相关的几项配置有 home、routes、initialRoute、onGenerateRoute、onUnknownRoute,它们分别对应着主页面 widget、路由表(根据路由找到对应 widget)、首次加载时的路由、路由生成器、未知路由代理(比如常见的 404 页面)。

    MaterialApp/CupertinoApp 的子结点都是 WidgetsApp,只不过他们给 WidgetsApp 传入了不同的参数,从而使得两种 Widget 的界面风格不一致。Navigator 就是在 WidgetsApp 中创建的,


    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,
         onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
           ? Navigator.defaultGenerateInitialRoutes
           : (NavigatorState navigator, String initialRouteName) {
             return widget.onGenerateInitialRoutes(initialRouteName);
           },
         onUnknownRoute: _onUnknownRoute,
         observers: widget.navigatorObservers,
       );
     }
     ...
    }

    在 WidgetsApp 的 build 中第一个创建的就是 Navigator,主要看一下它的参数,首先,_navigator 是一个 GlobalKey,使得 WidgetsApp 可以通过 key 调用 Navigator 的函数进行路由切换,也就是在 WidgetsBinding 中处理 native 的路由切换信息的时候,最终是由 WidgetsApp 完成的。另外这里的 _navigator 应该只在 WidgetsApp 中有使用,其他地方需要使用一般是直接调用 Navigator.of 获取,这个函数会沿着 element 树向上查找到 NavigatorState,所以在应用中切换路由是需要被 Navigator 包裹的,不过由于 WidgetsApp 中都有生成 Navigator,开发中也不必考虑这些。

    另外,就是关于底层获取上层 NavigatorElement 实例的方式,在 Element 树中有两种方式可以从底层获取到上层的实例,一种方式是使用 InheritedWidget,另一种就是直接沿着树向上查找(ancestorXXXOfExactType 系列),两种方式的原理基本是一致的,只不过 InheritedWidget 在建立树的过程中会一层层向下传递,而后者是使用的时候才向上查找,所以从这个角度来说使用 InheritedWidget 会高效些,但是 InheritedWidget 的优势不止如此,它是能够在数据发生改变的时候通知所有依赖它的结点进行更新,这也是 ancestorXXXOfExactType 系列所没有的。

    然后 initialRoute 规定了初始化时候的页面,由 WidgetsBinding.instance.window.defaultRouteName 和 widget.initialRoute 来决定,不过前者优先级更高,因为这个是 native 中指定的,以 android 为例,在启动 FlutterActivity 的时候可以传入 route 字段指定初始化页面。

    onGenerateRoute 和 onUnknownRoute 是获取 route 的策略,当 onGenerateRoute 没有命中时会调用 onUnknownRoute 给定一个默认的页面,onGenerateInitialRoutes 用于生产启动应用时的路由列表,它有一个默认实现 defaultGenerateInitialRoutes,会根据传递的 initialRouteName 选择不同的 Route,如果传入的 initialRouteName 并不是默认的主页面路由 Navigator.defaultRouteName,flutter 并不会将 initRoute 作为主页面,而是将默认路由入栈了之后再入栈 initRoute 对应的页面,所以如果在这之后再调用 popRoute,是会返回到主页面的

    observers 是路由切换的监听列表,可以由外部传入,在路由切换的时候做些操作,比如 HeroController 就是一个监听者。
    Navigator 是一个 StatefulWidget,在 NavigatorState 的 initState 中完成了将 initRoute 转换成 Route 的过程,并调用 push 将其入栈,生成 OverlayEntry,这个会继续传递给下层负责显示页面的 Overlay 负责展示。

    在 push 的过程中,route 会被转换成 OverlayEntry 列表存放,每一个 OverlayEntry 中存储一个 WidgetBuilder,从某种角度来说,OverlayEntry 可以被认为是一个页面。所有的页面的协调、展示是通过 Overlay 完成的,Overlay 是一个类似于 Stack 的结构,它可以展示多个子结点。在它的 initState 中,


    void initState() {
     super.initState();
     insertAll(widget.initialEntries);
    }

    会将 initialEntries 都存到 _entries 中。

    Overlay 作为一个能够根据路由确定展示页面的控件,它的实现其实比较简单:


    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<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,
     );
    }

    build 函数中,将所有的 OverlayEntry 分成了可见与不可见两部分,每一个 OverlayEntry 生成一个 _OverlayEntry,这是一个 StatefulWidget,它的作用主要是负责控制当前页重绘,都被封装成 然后再用  _Theatre 展示就完了,在 _Theatre 中,可见/不可见的子结点都会转成 Element,但是在绘制的时候,_Theatre 对应的 _RenderTheatre 只会把可见的子结点绘制出来。

    判断某一个 OverlayEntry 是否能够完全遮挡上一个 OverlayEntry 是通过它的 opaque 变量判断的,而 opaque 又是由 Route 给出的,在页面动画执行时,这个值会被设置成 false,然后在页面切换动画执行完了之后就会把 Route 的 opaque 参数赋值给它的 OverlayEntry,一般情况下,窗口对应的 Route 为 false,页面对应的 Route 为 true。

    所以说在页面切换之后,上一个页面始终都是存在于 element 树中的,只不过在 RenderObject 中没有将其绘制出来,这一点在 Flutter Outline 工具里面也能够体现。从这个角度也可以理解为,在 flutter 中页面越多,需要处理的步骤就越多,虽然不需要绘制底部的页面,但是整个树的基本遍历还是会有的,这部分也算是开销。

    _routeNamed

    flutter 中进行页面管理主要的依赖路由管理系统,它的入口就是 Navigator,它所管理的东西,本质上就是承载着用户页面的 Route,但是在 Navigator 中有很多函数是 XXXName 系列的,它们传的不是 Route,而是 RouteName,据个人理解,这个主要是方便开发引入的,我们可以在 MaterialApp/CupertinoApp 中直接传入路由表,每一个名字对应一个 WidgetBuilder,然后结合 pageRouteBuilder(这个可以自定义,不过 MaterialApp/CupertinoApp 都有默认实现,能够将 WidgetBuilder 转成 Route),便可以实现从 RouteName 到 Route 的转换。


    Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
     if (allowNull && widget.onGenerateRoute == null)
       return null;
     final RouteSettings settings = RouteSettings(
       name: name,
       arguments: arguments,
     );
     Route<T> route = widget.onGenerateRoute(settings) as Route<T>;
     if (route == null && !allowNull) {
       route = widget.onUnknownRoute(settings) as Route<T>;
     }
     return route;
    }

    这个过程分三步,生成 RouteSettings,调用 onGenerateRoute 从路由表中拿到对应的路由,如果无命中,就调用 onUnknownRoute 给一个类似于 404 页面的东西。

    onGenerateRoute 和 onUnknownRoute 在构建 Navigator 时传入,在 WidgetsApp 中实现,


    Route<dynamic> _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<dynamic> route = widget.pageRouteBuilder<dynamic>(
         settings,
         pageContentBuilder,
       );
       return route;
     }
     if (widget.onGenerateRoute != null)
       return widget.onGenerateRoute(settings);
     return null;
    }

    如果是默认的路由会直接使用给定的 home 页面(如果有),否则就直接到路由表查,所以本质上这里的 home 页面更多的是一种象征,身份的象征,没有也无所谓。另外路由表主要的产出是 WidgetBuilder,它需要经过一次包装,成为 Route 才是成品,或者如果不想使用路由表这种,也可以直接实现 onGenerateRoute 函数,根据 RouteSetting 直接生成 Route,这个就不仅仅是返回 WidgetBuilder 这么简单了,需要自己包装。

    onUnknownRoute 主要用于兜底,提供一个类似于 404 的页面,它也是需要直接返回 Route。

    _flushHistoryUpdates

    不知道从哪一个版本开始,flutter 的路由管理引入了状态,与之前每一个 push、pop 都单独实现不同,所有的路由切换操作都是用状态表示,同时所有的 route 都被封装成 _RouteEntry,它内部有着关于 Route 操作的实现,但都被划分为比较小的单元,且都依靠状态来执行。

    状态是一个具有递进关系的枚举,每一个 _RouteEntry 都有一个变量存放当前的状态,在 _flushHistoryUpdates 中会遍历所有的 _RouteEntry 然后根据它们当前的状态进行处理,同时处理完成之后会切换它们的状态,再进行其他处理,这样的好处很明显,所有的路由都放在一起处理之后,整个流程会变得更加清晰,且能够很大程度上进行代码复用,比如 push 和 pushReplacement 两种操作,这在之前是需要在两个方法中单独实现的,而现在他们则可以放在一起单独处理,不同的只有后者比前者会多一个 remove 的操作。

    关于 _flushHistoryUpdates 的处理步骤:


    void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
     assert(_debugLocked && !_debugUpdatingPage);
     // Clean up the list, sending updates to the routes that changed. Notably,
     // we don't send the didChangePrevious/didChangeNext updates to those that
     // did not change at this point, because we're not yet sure exactly what the
     // routes will be at the end of the day (some might get disposed).
     int index = _history.length - 1;
     _RouteEntry next;
     _RouteEntry entry = _history[index];
     _RouteEntry previous = index > 0 ? _history[index - 1] : null;
     bool canRemoveOrAdd = false; // Whether there is a fully opaque route on top to silently remove or add route underneath.
     Route<dynamic> poppedRoute; // The route that should trigger didPopNext on the top active route.
     bool seenTopActiveRoute = false; // Whether we've seen the route that would get didPopNext.
     final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
     while (index >= 0) {
       switch (entry.currentState) {
           // ...
       }
       index -= 1;
       next = entry;
       entry = previous;
       previous = index > 0 ? _history[index - 1] : null;
     }
     // Now that the list is clean, send the didChangeNext/didChangePrevious
     // notifications.
     _flushRouteAnnouncement();
     // Announces route name changes.
     final _RouteEntry lastEntry = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null);
     final String routeName = lastEntry?.route?.settings?.name;
     if (routeName != _lastAnnouncedRouteName) {
       RouteNotificationMessages.maybeNotifyRouteChange(routeName, _lastAnnouncedRouteName);
       _lastAnnouncedRouteName = routeName;
     }
     // Lastly, removes the overlay entries of all marked entries and disposes
     // them.
     for (final _RouteEntry entry in toBeDisposed) {
       for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
         overlayEntry.remove();
       entry.dispose();
     }
     if (rearrangeOverlay)
       overlay?.rearrange(_allRouteOverlayEntries);
    }

    以上是除了状态处理之外,一次 _flushHistoryUpdates 的全过程,首先它会遍历整个路由列表,根据状态做不同的处理,不过一般能够处理到的也不过最上层一两个,其余的多半是直接跳过的。处理完了之后,调用 _flushRouteAnnouncement 进行路由之间的前后链接,比如进行动画的联动等,


    void _flushRouteAnnouncement() {
     int index = _history.length - 1;
     while (index >= 0) {
       final _RouteEntry entry = _history[index];
       if (!entry.suitableForAnnouncement) {
         index -= 1;
         continue;
       }
       final _RouteEntry next = _getRouteAfter(index + 1, _RouteEntry.suitableForTransitionAnimationPredicate);
       if (next?.route != entry.lastAnnouncedNextRoute) {
         if (entry.shouldAnnounceChangeToNext(next?.route)) {
           entry.route.didChangeNext(next?.route);
         }
         entry.lastAnnouncedNextRoute = next?.route;
       }
       final _RouteEntry previous = _getRouteBefore(index - 1, _RouteEntry.suitableForTransitionAnimationPredicate);
       if (previous?.route != entry.lastAnnouncedPreviousRoute) {
         entry.route.didChangePrevious(previous?.route);
         entry.lastAnnouncedPreviousRoute = previous?.route;
       }
       index -= 1;
     }
    }

    其实现也比较清晰,对每一个 _RouteEntry,通过调用 didChangeNext 和 didChangePrevious 来建立联系,比如在 didChangeNext 中绑定当前 Route 的 secondaryAnimation 和下一个路由的 animation 进行动画联动,再比如在 didChangePrevious 中获取上一个路由的 title,这个可以用于 CupertinoNavigationBar 中 back 按钮展示上一页面的 title。
    然后调用 maybeNotifyRouteChange 发出通知,指定当前正在处于展示状态的 Route。

    最后,遍历 toBeDisposed 执行 _RouteEntry 的销毁,这个列表会保存上面循环处理过程中,确定需要移出的 _RouteEntry,通过调用 OverlayEntry remove 函数(它会将自己从 Overlay 中移除)和 OverlayEntry dispose 函数(它会调用 Route 的 dispose,进行资源释放,比如 TransitionRoute 中 AnimationController 销毁)。

    最后再看关于状态的处理,以下是所有的状态:


    enum _RouteLifecycle {
     staging, // we will wait for transition delegate to decide what to do with this route.
     //
     // routes that are present:
     //
     add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
     adding, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
     // routes that are ready for transition.
     push, // we'll want to run install, didPush, etc; a route added via push() and friends
     pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends
     pushing, // we're waiting for the future from didPush to complete
     replace, // we'll want to run install, didReplace, etc; a route added via replace() and friends
     idle, // route is being harmless
     //
     // routes that are not present:
     //
     // routes that should be included in route announcement and should still listen to transition changes.
     pop, // we'll want to call didPop
     remove, // we'll want to run didReplace/didRemove etc
     // routes should not be included in route announcement but should still listen to transition changes.
     popping, // we're waiting for the route to call finalizeRoute to switch to dispose
     removing, // we are waiting for subsequent routes to be done animating, then will switch to dispose
     // routes that are completely removed from the navigator and overlay.
     dispose, // we will dispose the route momentarily
     disposed, // we have disposed the route
    }

    本质上这些状态分为三类,add(处理初始化的时候直接添加),push(与 add 类似,但是增加了动画的处理),pop(处理页面移出),remove(移出某个页面,相对 pop 没有动画,也没有位置限制)。

    add

    add 方式添加路由目前还只用于在应用初始化是添加初始化页面使用,对应的是在 NavigatorState 的 initState 中,


    void initState() {
     super.initState();
     for (final NavigatorObserver observer in widget.observers) {
       assert(observer.navigator == null);
       observer._navigator = this;
     }
     String initialRoute = widget.initialRoute;
     if (widget.pages.isNotEmpty) {
       _history.addAll(
         widget.pages.map((Page<dynamic> page) => _RouteEntry(
           page.createRoute(context),
           initialState: _RouteLifecycle.add,
         ))
       );
     } else {
       // If there is no page provided, we will need to provide default route
       // to initialize the navigator.
       initialRoute = initialRoute ?? Navigator.defaultRouteName;
     }
     if (initialRoute != null) {
       _history.addAll(
         widget.onGenerateInitialRoutes(
           this,
           widget.initialRoute ?? Navigator.defaultRouteName
         ).map((Route<dynamic> route) =>
           _RouteEntry(
             route,
             initialState: _RouteLifecycle.add,
           ),
         ),
       );
     }
     _flushHistoryUpdates();
    }

    它会将从 onGenerateInitialRoutes 得来的所有初始路由转成 _RouteEntry 加入到 _history,此时它们的状态是 _RouteLifecycle.add,然后就是调用 _flushHistoryUpdates 进行处理。


    void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
     // ...
     while (index >= 0) {
       switch (entry.currentState) {
         case _RouteLifecycle.add:
           assert(rearrangeOverlay);
           entry.handleAdd(
             navigator: this,
           );
           assert(entry.currentState == _RouteLifecycle.adding);
           continue;
         case _RouteLifecycle.adding:
           if (canRemoveOrAdd || next == null) {
             entry.didAdd(
               navigator: this,
               previous: previous?.route,
               previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
               isNewFirst: next == null
             );
             assert(entry.currentState == _RouteLifecycle.idle);
             continue;
           }
           break;
         case _RouteLifecycle.idle:
           if (!seenTopActiveRoute && poppedRoute != null)
             entry.handleDidPopNext(poppedRoute);
           seenTopActiveRoute = true;
           // This route is idle, so we are allowed to remove subsequent (earlier)
           // routes that are waiting to be removed silently:
           canRemoveOrAdd = true;
           break;
           // ...
       }
       index -= 1;
       next = entry;
       entry = previous;
       previous = index > 0 ? _history[index - 1] : null;
     }
     // ...
    }

    add 路线主要会调用两个函数,handleAdd 和 didAdd,


    void handleAdd({ @required NavigatorState navigator}) {
     assert(currentState == _RouteLifecycle.add);
     assert(navigator != null);
     assert(navigator._debugLocked);
     assert(route._navigator == null);
     route._navigator = navigator;
     route.install();
     assert(route.overlayEntries.isNotEmpty);
     currentState = _RouteLifecycle.adding;
    }

    install 函数可以看作是 Route 的初始化函数,比如在 ModalRoute 中创建 ProxyAnimation 来管理一些动画的执行,在 TransitionRoute 中创建了用于执行切换动画的 AnimationController,在 OverlayRoute 中完成了当前 Route 的 OverlayEntry 的创建及插入。createOverlayEntries 用于创建 OverlayEntry,其实现在 ModalRoute,


    Iterable<OverlayEntry> createOverlayEntries() sync* {
     yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
     yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
    }

    每一个 Route 都能生成两个 OverlayEntry,一个是 _buildModalBarrier,它可以生成两个页面之间的屏障,我们可以利用它给新页面设置一个背景色,同时还支持动画过渡,另一个是 _buildModalScope,它生成的就是这个页面真正的内容,外部会有多层包装,最底层就是 WidgetBuilder 创建的 widget。

    大致看下两个函数的实现,


    Widget _buildModalBarrier(BuildContext context) {
     Widget barrier;
     if (barrierColor != null && !offstage) { // changedInternalState is called if these update
       assert(barrierColor != _kTransparent);
       final Animation<Color> color = animation.drive(
         ColorTween(
           begin: _kTransparent,
           end: barrierColor, // changedInternalState is called if this updates
         ).chain(_easeCurveTween),
       );
       barrier = AnimatedModalBarrier(
         color: color,
         dismissible: barrierDismissible, // changedInternalState is called if this updates
         semanticsLabel: barrierLabel, // changedInternalState is called if this updates
         barrierSemanticsDismissible: semanticsDismissible,
       );
     } else {
       barrier = ModalBarrier(
         dismissible: barrierDismissible, // changedInternalState is called if this updates
         semanticsLabel: barrierLabel, // changedInternalState is called if this updates
         barrierSemanticsDismissible: semanticsDismissible,
       );
     }
     return IgnorePointer(
       ignoring: animation.status == AnimationStatus.reverse || // changedInternalState is called when this updates
                 animation.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
       child: barrier,
     );
    }

    ModalBarrier 是两个 Route 之间的屏障,它可以通过颜色、拦截事件来表示两个 Route 的隔离,这些都是可以配置的,这里 IgnorePointer 的作用是为了在执行切换动画的时候无法响应时间。


    Widget _buildModalScope(BuildContext context) {
     return _modalScopeCache ??= _ModalScope<T>(
       key: _scopeKey,
       route: this,
       // _ModalScope calls buildTransitions() and buildChild(), defined above
     );
    }

    Widget build(BuildContext context) {
     return _ModalScopeStatus(
       route: widget.route,
       isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates
       canPop: widget.route.canPop, // _routeSetState is called if this updates
       child: Offstage(
         offstage: widget.route.offstage, // _routeSetState is called if this updates
         child: PageStorage(
           bucket: widget.route._storageBucket, // immutable
           child: FocusScope(
             node: focusScopeNode, // immutable
             child: RepaintBoundary(
               child: AnimatedBuilder(
                 animation: _listenable, // immutable
                 builder: (BuildContext context, Widget child) {
                   return widget.route.buildTransitions(
                     context,
                     widget.route.animation,
                     widget.route.secondaryAnimation,
                     IgnorePointer(
                       ignoring: widget.route.animation?.status == AnimationStatus.reverse,
                       child: child,
                     ),
                   );
                 },
                 child: _page ??= RepaintBoundary(
                   key: widget.route._subtreeKey, // immutable
                   child: Builder(
                     builder: (BuildContext context) {
                       return widget.route.buildPage(
                         context,
                         widget.route.animation,
                         widget.route.secondaryAnimation,
                       );
                     },
                   ),
                 ),
               ),
             ),
           ),
         ),
       ),
     );
    }

    _ModalScope 需要承载用户界面的展示,它的 build 函数可以看到在 widget.route.buildPage 出用户定义的页面之上有很多层,可以一层一层看下大致作用:

    • _ModalScopeStatus,继承自 InheritedWidget,用于给底层结点提供数据

    • Offstage,可以通过 offstage 变量控制是否绘制

    • PageStorage,它提供了一种存储策略,也就是 PageStorageBucket,这个类可以给某一个 BuildContext 绑定特定的数据,支持写入和读取,可用于某一个 widget 的状态存储等

    • FocusScope,用于焦点管理用,一般只有获取焦点的控件才能接收到按键信息等

    • RepaintBoundary,控制重绘范围,意在减少不必要的重绘

    • AnimatedBuilder,动画控制 Widget,会根据 animation 进行 rebuild

    • widget.route.buildTransitions,它在不同的 Route 中可以有不同的实现,比如 Android 的默认实现是自下向上渐入,ios 的默认实现是自右向左滑动,另外也可以通过自定义 Route 或自定义 ThemeData 实现自定义的切换动画,还有一点需要说明,Route 中的动画分为 animation 和 secondaryAnimation,其中 animation 定义了自己 push 时的动画,secondaryAnimation 定义的是新页面 push 时自己的动画,举个例子,在 ios 风格中,新页面自右向左滑动,上一个页面也会滑动,此时控制上一个页面滑动的动画就是 secondaryAnimation

    • IgnorePointer,同样是用于页面切换动画执行中,禁止用户操作

    • RepaintBoundary,这里的考量应该是考虑到上层有一个动画执行,所以这里包一下避免固定内容重绘

    • Builder,Builder 的唯一作用应该是提供 BuildContext,虽然说每一个 build 函数都有 BuildContext 参数,但这个是当前 Widget 的,而不是直属上级的,这可能有点抽象,比如说下面的 buildPage 需要使用 BuildContext 作为参数,那么如果它需要使用 context 的 ancestorStateOfType 的话,实际上就是从 _ModalScopeState 开始向上查找,而不是从 Builder 开始向上查找

    • widget.route.buildPage,这个函数内部就是使用 Route 的 WidgetBuilder 创建用户界面,当然不同的 Route 可能还会在这里再次进行包装

    来源:https://juejin.cn/post/6983338586238091272

    标签:flutter,路由
    0
    投稿

    猜你喜欢

  • C# 表达式目录树Expression的实现

    2023-04-03 22:57:32
  • java中replaceAll替换圆括号实例代码

    2023-09-30 16:29:54
  • Flutter倒计时/计时器的实现代码

    2023-07-01 03:50:50
  • WPF快速入门教程之绑定Binding

    2021-10-10 15:32:00
  • C#实现微信跳一跳小游戏的自动跳跃助手开发实战

    2022-12-11 02:49:08
  • Android开发实现TextView超链接5种方式源码实例

    2022-12-10 16:50:32
  • java简单实现斗地主发牌功能

    2023-06-18 16:22:44
  • Java中为什么start方法不能重复调用而run方法可以?

    2023-11-15 03:04:02
  • mybatis之如何获取表中某一列的最大值

    2022-03-26 08:38:53
  • C#中实现可变参数实例

    2022-03-27 15:48:07
  • 源码浅析Android中内存泄漏检测工具Leakcanary的使用

    2021-11-02 12:30:11
  • Java如何实现字符串每隔4位加空格

    2023-11-27 06:00:09
  • Android基于OpenCV实现Harris角点检测

    2023-07-16 12:19:47
  • 一文看懂JAVA设计模式之工厂模式

    2023-11-27 02:30:54
  • Android WorkManager浅谈

    2023-03-24 11:26:46
  • 详解Java中Duration类的使用方法

    2021-07-30 20:09:28
  • java 高并发中volatile的实现原理

    2022-11-24 19:58:22
  • C#浅拷贝和深拷贝实例解析

    2022-09-03 02:31:05
  • 解决使用RestTemplate时报错RestClientException的问题

    2023-05-27 19:46:36
  • java中单例模式讲解

    2022-05-22 14:24:07
  • asp之家 软件编程 m.aspxhome.com