Flutter系列十一:Flutter Navigator 2.0原理詳解

Navigator 2.0作為新一代的路由提供了申明式API蜓席,更加符合Flutter的風(fēng)格。Navigator 2.0向前兼容仔戈,新增了一些新的API推姻,使用的方式和Navigator 1.0相比有較大的差別。

本文將詳細解析Navigator 2.0的底層邏輯课兄,讓大家對它有一個深入的了解滓鸠,這樣在使用上會更加的得心應(yīng)手。

Navigator 2.0 誕生的背景

Flutter官方團隊改造路由主要有幾點原因:

  1. Navigator 1.0 只提供了一些push(), pushNamed()pop()等簡單的API第喳。實現(xiàn)壓入或者彈出多個頁面很困難糜俗,更難實現(xiàn)對棧內(nèi)中間頁面的移除,交換等操作曲饱;
  2. Flutter隨著2.0的到來實現(xiàn)了全平臺的支持悠抹,這樣也就新出現(xiàn)一些使用場景,譬如網(wǎng)頁修改URL地址等扩淀,這些就需要新的API來支持楔敌;
  3. Navigator 2.0滿足了嵌套路由的需求場景,這樣開發(fā)者在使用時就更加的靈活和方便驻谆;
  4. Navigator 2.0提供的是申明式API卵凑,解決了以前路由命令式編程的方式,讓編程的風(fēng)格統(tǒng)一胜臊。

Navigator 2.0API雖然比較的多勺卢,但是邏輯還是比較清晰的,我們來一個個的進行介紹象对。

Page

Page代表頁面不可變的的配置信息黑忱,代表一個頁面,類似于Widget配置信息轉(zhuǎn)換成Element, Page配置的信息會轉(zhuǎn)換成Route勒魔。

abstract class Page<T> extends RouteSettings {
  
  const Page({
    this.key,
    String? name,
    Object? arguments,
    this.restorationId,
  }) : super(name: name, arguments: arguments);


  bool canUpdate(Page<dynamic> other) {
    return other.runtimeType == runtimeType &&
           other.key == key;
  }

  @factory
  Route<T> createRoute(BuildContext context);
}
  1. createRoute就是轉(zhuǎn)換成Route的方法甫煞;
  2. canUpdate的實現(xiàn)方式和Widget的一樣,也是用于diff算法冠绢。

RouteSettings

Page的父類RouteSettings僅僅用來保存namearguments這兩個值抚吠。

const RouteSettings({
    this.name,
    this.arguments,
});

Route

Route代表一個頁面,是Navigator棧中真正管理的內(nèi)容弟胀。

abstract class Route<T> {
    
    // 1   
    RouteSettings get settings => _settings;
    NavigatorState? get navigator => _navigator;

    // 2
    List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
    
    // 3
    void install() {}
    TickerFuture didPush() {}
    ...
    
}
  1. Route持有了配置對象page和管理它的navigator對象;
  2. Route還持有一個OverlayEntry數(shù)組楷力,OverlayEntry放置在類似于StackOverlay上蕊玷,我們寫的頁面就是放置在一個OverlayEntry上的;
  3. Route還定義了一些協(xié)議方法需要子類覆寫弥雹,這些方法主要是route的狀態(tài)變化后收到的回調(diào)函數(shù),這些函數(shù)調(diào)用主要來自于_RouteEntry延届。
方法 調(diào)用時機
install 被插入navigator
didPush 動畫進入顯示
didAdd 直接顯示
didReplace 替換舊的route
didPop 請求pop頁面
didComplete pop完成后
didPopNext 當(dāng)前route后面的route被pop
didChangeNext 當(dāng)前route后面的route被替換
didChangePrevious 當(dāng)前route前面的route被替換
changedInternalState 當(dāng)前routestate變化后
changedExternalState 當(dāng)前routenavigator變化后

MaterialPage_PageBasedMaterialPageRoute

我們可以直接使用系統(tǒng)給我們提供的Page類剪勿,也可以自定義繼承自Page的類。我們來看看官方給我們提供的MaterialPage的邏輯方庭。

MaterialPage的Route是_PageBasedMaterialPageRoute類厕吉,它的繼承邏輯是:_PageBasedMaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute + LocalHistoryRoute -> Route

LocalHistoryRoute

LocalHistoryRoute可以給Route添加一些LocalHistoryEntry械念。當(dāng)LocalHistoryEntry不為空時头朱,didPop方法調(diào)用的時候會移除最后一個LocalHistoryEntry,否則Route就要被pop了龄减。

OverlayRoute

OverlayRoute主要是持有Route對應(yīng)的OverlayEntry數(shù)組项钮,這個數(shù)組是子類在被插入navigator的時候?qū)ζ溥M行賦值的。

abstract class OverlayRoute<T> extends Route<T> {
    @factory
    Iterable<OverlayEntry> createOverlayEntries();
    
    List<OverlayEntry> get overlayEntries => _overlayEntries;
    
    void install() {
        _overlayEntries.addAll(createOverlayEntries());
        super.install();
    }
}
TransitionRoute

TransitionRoute是主要是負(fù)責(zé)動畫部分希停。

abstract class TransitionRoute<T> extends OverlayRoute<T> {
    
    Animation<double>? get animation => _animation;
    Animation<double>? get secondaryAnimation => _secondaryAnimation;
    
    void install() {
        _animation = createAnimation()
          ..addStatusListener(_handleStatusChanged);
        super.install();
    }
    
    TickerFuture didPush() {
        super.didPush();
        return _controller!.forward();
    }
    
    void didAdd() {
        super.didAdd();
        _controller!.value = _controller!.upperBound;
    }
    
    bool didPop(T? result) {
        _controller!.reverse();
        return super.didPop(result);
    }

    void didPopNext(Route<dynamic> nextRoute) {
        _updateSecondaryAnimation(nextRoute);
        super.didPopNext(nextRoute);
    }

    void didChangeNext(Route<dynamic>? nextRoute) {
        _updateSecondaryAnimation(nextRoute);
        super.didChangeNext(nextRoute);
    }
}
  1. TransitionRoute_animationsecondaryAnimation兩個動畫,前者負(fù)責(zé)當(dāng)前Routepushpop動畫烁巫,后者負(fù)責(zé)下一個Route進行pushpop時本身這個Route的動畫。
  2. _animationinstall就生成了宠能,secondaryAnimation可以大部分情況下就是下一個Route_animation, 所以didPopNextdidChangeNext時需要更新secondaryAnimation亚隙。
  3. 如果不需要動畫時調(diào)用的是didAdd方法,Route是被動調(diào)用的這個方法违崇,其實是_RouteEntry根據(jù)(Navigator確定的)狀態(tài)判斷調(diào)用的這個方法阿弃。
ModalRoute

ModalRoute主要的作用是阻止除最上層的Route之外的Route進行用戶交互,其中的知識點也是非常豐富的羞延。

abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
    
  Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
  }

}
  1. ModalRoute生成了兩個非常重要的OverlayEntry---_modalBarrier_modalScope渣淳。
  2. _modalBarrier實現(xiàn)了阻止用戶對最上層Route之外的Route進行用戶交互的功能;
  3. _modalScope會持有router自身伴箩,_modalScope在構(gòu)建的時候就會調(diào)用routerbuildTransitionsbuildChild方法水由,參數(shù)都包含routeranimationsecondaryAnimation,也就是TransitionRoute中的兩個動畫屬性;
Widget _buildModalScope(BuildContext context) {
    return _modalScopeCache ??= Semantics(
      sortKey: const OrdinalSortKey(0.0),
      child: _ModalScope<T>(
        key: _scopeKey,
        route: this,
        // _ModalScope calls buildTransitions() and buildChild(), defined above
      )
    );
}

Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);

Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return child;
}

我們接下來看看_ModalScope_ModalScopeState的內(nèi)容:

class _ModalScopeState<T> extends State<_ModalScope<T>> {
    
    late Listenable _listenable;
    
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');
    
    void initState() {
        super.initState();
        final List<Listenable> animations = <Listenable>[
          if (widget.route.animation != null) widget.route.animation!,
          if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation!,
        ];
        _listenable = Listenable.merge(animations);
        if (widget.route.isCurrent) {
          widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);
        }
    }
}
  1. _listenablerouteanimationsecondaryAnimation的組合赛蔫;
  2. focusScopeNode是焦點砂客,初始化的時候?qū)?code>navigator的焦點設(shè)置為這個焦點,這樣就實現(xiàn)了最上層的Route才獲取到焦點呵恢,屏蔽對其他Route的焦點獲染现怠;
  Widget build(BuildContext context) {
    // 1 RestorationScope
    return AnimatedBuilder(
      animation: widget.route.restorationScopeId,
      builder: (BuildContext context, Widget? child) {
        return RestorationScope(
          restorationId: widget.route.restorationScopeId.value,
          child: child!,
        );
      },
      // 2 _ModalScopeStatus
      child: _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: Builder(
              builder: (BuildContext context) {
                return Actions(
                  actions: <Type, Action<Intent>>{
                    DismissIntent: _DismissModalAction(context),
                  },
                  child: PrimaryScrollController(
                    controller: primaryScrollController,
                    child: FocusScope(
                      node: focusScopeNode, // immutable
                      // 3 RepaintBoundary
                      child: RepaintBoundary(
                        // 4. AnimatedBuilder
                        child: AnimatedBuilder(
                          animation: _listenable, // immutable
                          builder: (BuildContext context, Widget? child) {
                            // 5. buildTransitions
                            return widget.route.buildTransitions(
                              context,
                              widget.route.animation!,
                              widget.route.secondaryAnimation!,
                              AnimatedBuilder(
                                animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
                                builder: (BuildContext context, Widget? child) {
                                  final bool ignoreEvents = _shouldIgnoreFocusRequest;
                                  focusScopeNode.canRequestFocus = !ignoreEvents;
                                  return IgnorePointer(
                                    ignoring: ignoreEvents,
                                    child: child,
                                  );
                                },
                                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!,
                                );
                              },
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ),
      ),
    );
  }

_ModalScopeStatebuild方法是設(shè)計非常精妙的一個方法:

  1. RestorationScope負(fù)責(zé)Route用于恢復(fù)數(shù)據(jù)的作用渗钉;
  2. _ModalScopeStatusInheritedWidget彤恶,它保持對Route的引用钞钙,所以我們在調(diào)用ModalRoute.of(contex)獲取頁面?zhèn)鲄r,就是通過獲取的這個_ModalScopeStatus声离,再找到對應(yīng)的傳參芒炼。
  3. 中間放置了一個RepaintBoundary可以限制重繪的區(qū)域,這樣可以提高進行動畫時繪制的效率术徊;
  4. 最底層的AnimatedBuilder這個Widget是核心本刽,這個AnimatedBuilderchild是由route.buildPage()這個方法創(chuàng)建的,其實就是我們Pagechild赠涮,即開發(fā)者寫的頁面內(nèi)容子寓;這個AnimatedBuilderbuilder方法中調(diào)用了route.buildTransitions(),它驅(qū)動動畫是_listenable,也就是說animationsecondaryAnimation都能驅(qū)動它的動畫過程笋除。這其實很好理解:當(dāng)前Routepoppush和下個Routepoppush都會觸發(fā)動畫的產(chǎn)生斜友。
PageRoute

PageRoute主要就是讓最上層下面的Route不可見,點擊_modalBarrier不讓當(dāng)前RouteNavigator棧中彈出垃它。

abstract class PageRoute<T> extends ModalRoute<T> {

  @override
  bool get opaque => true;

  @override
  bool get barrierDismissible => false;

}
_PageBasedMaterialPageRoute

_PageBasedMaterialPageRoute的作用是覆寫了buildPage方法, 返回的是開發(fā)者寫的界面鲜屏;

class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
    Widget buildContent(BuildContext context) {
        return _page.child;
    }
}

官方為我們提供了默認(rèn)的poppush動畫,它們就在混入的MaterialRouteTransitionMixin中實現(xiàn)的国拇。MaterialRouteTransitionMixin會根據(jù)不同的平臺有不同的實現(xiàn)墙歪,iOS是左右的動畫,Android是上下的動畫贝奇,web也是左右動畫虹菲。

我們以iOS為例,其最后使用的是CupertinoPageTransition這個類的方法:

SlideTransition(
    position: _secondaryPositionAnimation,
    textDirection: textDirection,
    transformHitTests: false,
    child: SlideTransition(
    position: _primaryPositionAnimation,
    textDirection: textDirection,
    child: DecoratedBoxTransition(
        decoration: _primaryShadowAnimation,
        child: child,
    ),
)

看到SlideTransition嵌套到一個child上是不是很疑惑掉瞳?兩個動畫用在一個Widget上毕源?

先解釋下其他參數(shù):

  1. textDirection決定了滑動的方法,因為有些語言是從右到左排序的陕习;
  2. transformHitTests設(shè)置為flase,點擊事件的響應(yīng)位置不受動畫的影響霎褐;
  3. _primaryShadowAnimation是設(shè)置了一個動畫中的陰影。

_secondaryPositionAnimation是從Offset.zeroOffset(-1.0/3.0, 0.0)该镣,正常情況下就是從右往左移動1/3的屏幕寬度冻璃。

final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
  begin: Offset.zero,
  end: const Offset(-1.0/3.0, 0.0),
);

_primaryPositionAnimation是從Offset(1.0, 0.0)Offset.zero,正常情況下就是從不可見的屏幕右邊移動到屏幕最左邊损合,然后占據(jù)整個屏幕寬度省艳。

final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
  begin: const Offset(1.0, 0.0),
  end: Offset.zero,
);

我們接下來解釋下pop一個Route時候的動畫邏輯, Animation:0->1

  1. 新加的Route是被_primaryPositionAnimation直接驅(qū)動的,也就是執(zhí)行了從右到左的_kRightMiddleTween動畫;
  2. _secondaryPositionAnimation只是被修改了值嫁审,我們前面TransitionRoute的介紹中提到過跋炕,新加入Routeanimation賦值給了前一個RoutesecondaryAnimation屬性。_ModalScopeState中介紹過secondaryAnimation也能驅(qū)動Route的動畫律适,也就是說前一個Route也能產(chǎn)生一個_kMiddleLeftTween動畫辐烂;

概括:

新加的Route通過animation驅(qū)動從屏幕右邊移動到左邊的動畫遏插,animation賦值給了前一個RoutesecondaryAnimation驅(qū)動前一個Route向左移動1/3個屏幕位置。

push的邏輯類似纠修,只是一個反向的動畫reverse胳嘲。前一個RoutesecondaryAnimation的驅(qū)動下右移了1/3屏幕寬度,當(dāng)前的Routeanimation驅(qū)動下移出屏幕扣草。

我們可以點擊Flutter DevToolsSlow Animations看看動畫的慢放過程:

動畫
階段總結(jié)
總結(jié)

_RouteEntry

Navigator不是直接操作的Route了牛,而是Route的封裝類_RouteEntry

_RouteEntry(
    this.route, 
    {
      required _RouteLifecycle initialState,
      this.restorationInformation,
    })

_RouteEntry除了持有route外德召,還持有一個_RouteLifecycle,即路由狀態(tài)汽纤。

函數(shù)則主要是修改_RouteLifecycle狀態(tài)的函數(shù)上岗,譬如markForPush,markForAdd,markForPop,markForRemove,markForComplete等。此外還有_RouteLifecycle被標(biāo)記后對Route進行操作函數(shù)蕴坪,譬如handlePush肴掷,handleAdd,handlePop,remove等。

Navigator

Navigator({
    Key? key,
    this.pages = const <Page<dynamic>>[],
    // ...
})

Navigator的構(gòu)造方法中有一個關(guān)鍵的屬性pages,Navigator會將傳入的pages會轉(zhuǎn)換成Routes對應(yīng)的_RouteEntry數(shù)組背传。

實現(xiàn)申明式編程的邏輯就是修改這個pages中的內(nèi)容呆瞻,Navigator會自動實現(xiàn)對應(yīng)的跳轉(zhuǎn),返回径玖,替換等操作痴脾。Navigator.push,Navigator.pop等以前使用的方法就被將不是開發(fā)者需要考慮的使用方法了。

我們接下來分析NavigatorState的重要代碼梳星。

class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
    
    List<_RouteEntry> _history = <_RouteEntry>[];
    
    late GlobalKey<OverlayState> _overlayKey;
    OverlayState? get overlay => _overlayKey.currentState;
    
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
    
}
  1. _history就是pages中每個Page通過createRoute生成的_RouteEntry數(shù)組赞赖;
  2. OverlayStateoverlay代表的就是OverLay,它負(fù)責(zé)擺放每個RouteoverlayEntries數(shù)組冤灾;OverLay就相當(dāng)于一個Stack前域,專門用于放置OverlayEntry

NavigatorState的核心方法是didUpdateWidget方法, 其調(diào)用了一個_updatePages()方法:

void didUpdateWidget(Navigator oldWidget) {
    _updatePages();
}

_updatePages方法的主要作用是對pages進行diff比對韵吨,更新_history數(shù)組中每個_routeEntry_RouteLifecycle, 最后調(diào)用_flushHistoryUpdates()方法匿垄。

_routeEntry比對的方法和MultiChildRenderObjectElement的比對方法是一樣的,先前往后比對能復(fù)用的元素归粉,然后從后往前比對能復(fù)用的元素椿疗,然后對剩下的元素進行復(fù)用或者新建,不能復(fù)用的元素進行銷毀糠悼。

void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
    final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
    while (index >= 0) {
      switch (entry!.currentState) {
        case _RouteLifecycle.push:
        case _RouteLifecycle.pushReplace:
        case _RouteLifecycle.replace:
          entry.handlePush(
            navigator: this,
            previous: previous?.route,
            previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
            isNewFirst: next == null,
          );
          if (entry.currentState == _RouteLifecycle.idle) {
            continue;
          }
          break;
        // ...
      }
      index -= 1;
      next = entry;
      entry = previous;
      previous = index > 0 ? _history[index - 1] : null;
    }

    _flushObserverNotifications();

    _flushRouteAnnouncement();

    for (final _RouteEntry entry in toBeDisposed) {
      for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
        overlayEntry.remove();
      entry.dispose();
    }
    
    if (rearrangeOverlay) {
      overlay?.rearrange(_allRouteOverlayEntries);
    }
}
  1. 根據(jù)每個_RouteEntry_RouteLifecycle調(diào)用對應(yīng)的方法变丧,例如如果Route被標(biāo)記為_RouteLifecycle.push,則調(diào)用handlePush方法,這樣此Route就會調(diào)用install方法插入Navigator的樹中绢掰,然后進行動畫痒蓬;
  2. _flushObserverNotifications是對每個_NavigatorObservation監(jiān)聽者進行通知童擎;
  3. _flushRouteAnnouncement主要是對每個Route的前后關(guān)系進行梳理更新,secondaryAnimation的更新就是這個時候進行的攻晒;
  4. 將不需要的_RouteEntryoverlayEntriesOverlay上移除顾复,因為不需要再顯示了;
  5. 然后將所有的_RouteEntryoverlayEntries更新到Overlay上,代碼在build方法中可以看到添加的邏輯如下鲁捏。
Widget build(BuildContext context) {
    return HeroControllerScope.none(
      child: 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: UnmanagedRestorationScope(
              bucket: bucket,
              child: Overlay(
                key: _overlayKey,
                initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
              ),
            ),
          ),
        ),
      ),
    );
  }

順便提一下HeroControllerScope是負(fù)責(zé)進行Hero動畫的的Widget芯砸,類似于Android中的共享元素動畫

階段總結(jié)
階段總結(jié)

到目前為止给梅,我們通過切換Navigator的page就能夠?qū)崿F(xiàn)路由切換了假丧,是不是文章就結(jié)束了?沒有动羽,因為Navigator 2.0是為Flutter 2.0 的全平臺而生的包帚,目前還沒有解決一些問題,例如編輯瀏覽器網(wǎng)址运吓,網(wǎng)頁返回渴邦,安卓物理鍵返回等功能。

Router

Router({
    Key? key,
    this.routeInformationProvider,
    this.routeInformationParser,
    required this.routerDelegate,
    this.backButtonDispatcher,
  })
  
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<T>? routeInformationParser;
final RouterDelegate<T> routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;

我們看到Router有四個屬性拘哨,RouteInformationProvider路由信息提供者谋梭,RouteInformationParser路由信息解析者,RouterDelegate路由信息的處理代理倦青,BackButtonDispatcher返回處理的分發(fā)者瓮床。他們四個協(xié)同作用,共同實現(xiàn)路由的功能产镐。

RouteInformation

上面說的到路由信息就是指RouteInformation纤垂,包括路由的路徑location和路由對應(yīng)的狀態(tài)state。這里所指的狀態(tài)就是數(shù)據(jù)磷账。

class RouteInformation {

  final String? location;
  final Object? state;
}
RouteInformationProvider

RouteInformationProvider只有一個抽象方法routerReportsNewRouteInformation峭沦,這個方法的作用是根據(jù)RouteInformation進行一些額外的操作。

abstract class RouteInformationProvider extends ValueListenable<RouteInformation?> {
  void routerReportsNewRouteInformation(RouteInformation routeInformation) {}
}

系統(tǒng)默認(rèn)使用的是PlatformRouteInformationProvider, 它的routerReportsNewRouteInformation方法中回調(diào)了系統(tǒng)路由的更新逃糟,例如瀏覽器就會在History棧中新增一條歷史訪問記錄:

class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {

    void routerReportsNewRouteInformation(RouteInformation routeInformation) {
        SystemNavigator.routeInformationUpdated(
          location: routeInformation.location!,
          state: routeInformation.state,
        );
        _value = routeInformation;
    }

}
RouteInformationParser

這個類的作用是對T頁面模型和RouteInformation路由信息進行相互轉(zhuǎn)換:

abstract class RouteInformationParser<T> {
  
  Future<T> parseRouteInformation(RouteInformation routeInformation);

  RouteInformation? restoreRouteInformation(T configuration) => null;
}

parseRouteInformation這個方法主要是解析初始路由的時候會使用到吼鱼,例如 根據(jù)RouteInformation(location: "/")顯示啟動頁面;

restoreRouteInformation這個方法就是根據(jù)T頁面模型生成對應(yīng)的RouteInformation绰咽。

RouterDelegate

RouterDelegate顧名思義就是代替Router工作的類菇肃,它包括根據(jù)T頁面模型添加一個頁面,pop一個頁面取募,提供構(gòu)建的內(nèi)容等琐谤。

abstract class RouterDelegate<T> extends Listenable {
  
  Future<void> setInitialRoutePath(T configuration) {
    return setNewRoutePath(configuration);
  }

  Future<void> setNewRoutePath(T configuration);

  Future<bool> popRoute();

  T? get currentConfiguration => null;

  Widget build(BuildContext context);
}

可以混入PopNavigatorRouterDelegateMixinpopRoute方法,就不用自己去實現(xiàn)了玩敏。

我們從源碼角度看看RouteInformationProvider斗忌,RouteInformationParserRouterDelegate他們?nèi)咴诔跏蓟酚墒侨绾螌崿F(xiàn)的:

class _RouterState<T> extends State<Router<T>> {

  void initState() {
    super.initState();
    if (widget.routeInformationProvider != null) {
      _processInitialRoute();
    }
  }

  void _processInitialRoute() {
    _currentRouteInformationParserTransaction = Object();
    _currentRouterDelegateTransaction = Object();
    _lastSeenLocation = widget.routeInformationProvider!.value!.location;
    widget.routeInformationParser!
      .parseRouteInformation(widget.routeInformationProvider!.value!)
      .then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
      .then<void>(widget.routerDelegate.setInitialRoutePath)
      .then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
      .then<void>(_rebuild);
  }    
    
}

_processInitialRoute方法中我們看到了质礼,routeInformationParser解析routeInformationProvidervalue,然后routerDelegate根據(jù)這個解析的結(jié)果去調(diào)用setNewRoutePath設(shè)置路由织阳。

routeInformationProvider -> routeInformationParser -> routerDelegate -> (setNewRoutePath)

RouterDelegate的覆寫案例:

class MyRouterDelegate extends RouterDelegate<PageConfiguration>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<PageConfiguration> {
    
    final List<Page> _pages = [];
    
    final AppState appState;
    final GlobalKey<NavigatorState> navigatorKey;
    
    MyRouterDelegate(this.appState) : navigatorKey = GlobalKey() {
        appState.addListener(() {
          notifyListeners();
        });
    }

    List<MaterialPage> get pages => List.unmodifiable(_pages);
        
    
    Future<bool> popRoute() {
        _removePage(_pages.last);
        return Future.value(false);
    }
    
    Future<void> setNewRoutePath(PageConfiguration configuration) {
        if (shouldAddPage) {
          _pages.clear();
          addPage(configuration);
        }
        return SynchronousFuture(null);
    }
        
    Widget build(BuildContext context) {
        return Navigator(
          key: navigatorKey,
          onPopPage: _onPopPage,
          pages: buildPages(),
        );
    }
    
}
  1. MyRouterDelegate_pages屬性眶蕉,這個屬性作為NavigatorpagesappState是狀態(tài)管理的數(shù)據(jù)唧躲,用這個數(shù)據(jù)去驅(qū)動MyRouterDelegate的觀察者也就是Router即去重構(gòu)造挽,這樣Navigator也就會重構(gòu)了。
  2. popRoute_pages的最后一個頁面刪掉弄痹,通知Router即去重構(gòu)饭入,更新Navigator
  3. setNewRoutePath_pages添加對應(yīng)的Page肛真,通知Router即去重構(gòu)Navigator谐丢。

BackButtonDispatcher

BackButtonDispatcher主要就是解決安卓,網(wǎng)頁等物理返回的事件毁欣。它有兩個子類RootBackButtonDispatcherChildBackButtonDispatcher可以解決Router的嵌套問題庇谆。

BackButtonDispatcher的返回處理可以直接交給RouterDelegate去處理岳掐,例如下面的邏輯:

class MyBackButtonDispatcher extends RootBackButtonDispatcher {

  final MyRouterDelegate _routerDelegate;

  MyBackButtonDispatcher(this._routerDelegate)
      : super();

  // 3
  @override
  Future<bool> didPopRoute() {
    return _routerDelegate.popRoute();
  }

}
最后總結(jié)
最后總結(jié)

總結(jié)

Navigator 2.0的功能更加強大了凭疮,使用方式也變得更加Flutter了。但是變得更復(fù)雜了串述,這樣對學(xué)習(xí)和使用成本造成了很大的困擾执解,這方面也是很多人認(rèn)為Navigator 2.0是一個失敗的改造的原因。

本文主要從源碼角度分析了Navigator 2.0的實現(xiàn)邏輯纲酗,原理清楚后寫代碼應(yīng)該還是很簡單的衰腌。

如果你需要Demo,可以參閱下面兩篇文章的代碼觅赊,特別是第一篇文章的代碼非常具有參考價值:

Flutter Navigator 2.0 and Deep Links

Learning Flutter’s new navigation and routing system

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載右蕊,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末吮螺,一起剝皮案震驚了整個濱河市饶囚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸠补,老刑警劉巖萝风,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異紫岩,居然都是意外死亡规惰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門泉蝌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歇万,“玉大人揩晴,你說我怎么就攤上這事《榛ǎ” “怎么了文狱?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缘挽。 經(jīng)常有香客問我瞄崇,道長,這世上最難降的妖魔是什么壕曼? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮摹蘑,結(jié)果婚禮上轧飞,老公的妹妹穿的比我還像新娘衅鹿。我一直安慰自己,他們只是感情好大渤,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布掸绞。 她就那樣靜靜地躺著泵三,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衔掸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天较曼,我揣著相機與錄音捷犹,去河邊找鬼埃疫。 笑死,一個胖子當(dāng)著我的面吹牛栓霜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播销凑,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼澎蛛!你這毒婦竟也來了蜕窿?” 一聲冷哼從身側(cè)響起桐经,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阴挣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茎芭,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡梅桩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年摘投,在試婚紗的時候發(fā)現(xiàn)自己被綠了虹蓄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幸撕。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡坐儿,死狀恐怖貌矿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黑低,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布蕾管,位于F島的核電站菩暗,受9級特大地震影響停团,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塞蹭,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一番电、第九天 我趴在偏房一處隱蔽的房頂上張望辆琅。 院中可真熱鬧,春花似錦娩井、人聲如沸似袁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽著瓶。三九已至材原,卻和暖如春季眷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背威酒。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工兼搏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裳朋。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓鲤嫡,卻偏偏與公主長得像绑莺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诫肠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容