热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Flutter系列十一:FlutterNavigator2.0原理详解

Navigator2.0作为新一代的路由提供了申明式的API,更加符

Navigator 2.0作为新一代的路由提供了申明式API,更加符合Flutter的风格。Navigator 2.0向前兼容,新增了一些新的API,使用的方式和Navigator 1.0相比有较大的差别。

本文将详细解析Navigator 2.0的底层逻辑,让大家对它有一个深入的了解,这样在使用上会更加的得心应手。

Navigator 2.0 诞生的背景

Flutter官方团队改造路由主要有几点原因:

  1. Navigator 1.0 只提供了一些push(), pushNamed()pop()等简单的API。实现压入或者弹出多个页面很困难,更难实现对栈内中间页面的移除,交换等操作;
  2. Flutter随着2.0的到来实现了全平台的支持,这样也就新出现一些使用场景,譬如网页修改URL地址等,这些就需要新的API来支持;
  3. Navigator 2.0满足了嵌套路由的需求场景,这样开发者在使用时就更加的灵活和方便;
  4. Navigator 2.0提供的是申明式API,解决了以前路由命令式编程的方式,让编程的风格统一。

Navigator 2.0API虽然比较的多,但是逻辑还是比较清晰的,我们来一个个的进行介绍。

Page

Page代表页面不可变的的配置信息,代表一个页面,类似于Widget配置信息转换成Element, Page配置的信息会转换成Route

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


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

  @factory
  Route createRoute(BuildContext context);
}
  1. createRoute就是转换成Route的方法;
  2. canUpdate的实现方式和Widget的一样,也是用于diff算法。

RouteSettings

Page的父类RouteSettings仅仅用来保存namearguments这两个值。

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

Route

Route代表一个页面,是Navigator栈中真正管理的内容。

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

    // 2
    List get overlayEntries => const [];
    
    // 3
    void install() {}
    TickerFuture didPush() {}
    ...
    
}
  1. Route持有了配置对象page和管理它的navigator对象;
  2. Route还持有一个OverlayEntry数组,OverlayEntry放置在类似于StackOverlay上,我们写的页面就是放置在一个OverlayEntry上的;
  3. Route还定义了一些协议方法需要子类覆写,这些方法主要是route的状态变化后收到的回调函数,这些函数调用主要来自于_RouteEntry
方法 调用时机
install 被插入navigator
didPush 动画进入显示
didAdd 直接显示
didReplace 替换旧的route
didPop 请求pop页面
didComplete pop完成后
didPopNext 当前route后面的route被pop
didChangeNext 当前route后面的route被替换
didChangePrevious 当前route前面的route被替换
changedInternalState 当前routestate变化后
changedExternalState 当前routenavigator变化后

MaterialPage_PageBasedMaterialPageRoute

我们可以直接使用系统给我们提供的Page类,也可以自定义继承自Page的类。我们来看看官方给我们提供的MaterialPage的逻辑。

MaterialPage的Route是_PageBasedMaterialPageRoute类,它的继承逻辑是:_PageBasedMaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute + LocalHistoryRoute -> Route

LocalHistoryRoute

LocalHistoryRoute可以给Route添加一些LocalHistoryEntry。当LocalHistoryEntry不为空时,didPop方法调用的时候会移除最后一个LocalHistoryEntry,否则Route就要被pop了。

OverlayRoute

OverlayRoute主要是持有Route对应的OverlayEntry数组,这个数组是子类在被插入navigator的时候对其进行赋值的。

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

TransitionRoute是主要是负责动画部分。

abstract class TransitionRoute extends OverlayRoute {
    
    Animation? get animation => _animation;
    Animation? 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 nextRoute) {
        _updateSecondaryAnimation(nextRoute);
        super.didPopNext(nextRoute);
    }

    void didChangeNext(Route? nextRoute) {
        _updateSecondaryAnimation(nextRoute);
        super.didChangeNext(nextRoute);
    }
}
  1. TransitionRoute_animationsecondaryAnimation两个动画,前者负责当前Routepushpop动画,后者负责下一个Route进行pushpop时本身这个Route的动画。
  2. _animationinstall就生成了,secondaryAnimation可以大部分情况下就是下一个Route_animation, 所以didPopNextdidChangeNext时需要更新secondaryAnimation
  3. 如果不需要动画时调用的是didAdd方法,Route是被动调用的这个方法,其实是_RouteEntry根据(Navigator确定的)状态判断调用的这个方法。
ModalRoute

ModalRoute主要的作用是阻止除最上层的Route之外的Route进行用户交互,其中的知识点也是非常丰富的。

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

}
  1. ModalRoute生成了两个非常重要的OverlayEntry_modalBarrier_modalScope
  2. _modalBarrier实现了阻止用户对最上层Route之外的Route进行用户交互的功能;
  3. _modalScope会持有router自身,_modalScope在构建的时候就会调用routerbuildTransitionsbuildChild方法,参数都包含routeranimationsecondaryAnimation,也就是TransitionRoute中的两个动画属性;
Widget _buildModalScope(BuildContext context) {
    return _modalScopeCache ??= Semantics(
      sortKey: const OrdinalSortKey(0.0),
      child: _ModalScope(
        key: _scopeKey,
        route: this,
        // _ModalScope calls buildTransitions() and buildChild(), defined above
      )
    );
}

Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation);

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

我们接下来看看_ModalScope_ModalScopeState的内容:

class _ModalScopeState extends State> {
    
    late Listenable _listenable;
    
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');
    
    void initState() {
        super.initState();
        final List animatiOns= [
          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是焦点,初始化的时候将navigator的焦点设置为这个焦点,这样就实现了最上层的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: >{
                    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(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方法是设计非常精妙的一个方法:

  1. RestorationScope负责Route用于恢复数据的作用;
  2. _ModalScopeStatusInheritedWidget,它保持对Route的引用,所以我们在调用ModalRoute.of(contex)获取页面传参时,就是通过获取的这个_ModalScopeStatus,再找到对应的传参。
  3. 中间放置了一个RepaintBoundary可以限制重绘的区域,这样可以提高进行动画时绘制的效率;
  4. 最底层的AnimatedBuilder这个Widget是核心,这个AnimatedBuilderchild是由route.buildPage()这个方法创建的,其实就是我们Pagechild,即开发者写的页面内容;这个AnimatedBuilderbuilder方法中调用了route.buildTransitions(),它驱动动画是_listenable,也就是说animationsecondaryAnimation都能驱动它的动画过程。这其实很好理解:当前Routepoppush和下个Routepoppush都会触发动画的产生。
PageRoute

PageRoute主要就是让最上层下面的Route不可见,点击_modalBarrier不让当前RouteNavigator栈中弹出。

abstract class PageRoute extends ModalRoute {

  @override
  bool get opaque => true;

  @override
  bool get barrierDismissible => false;

}
_PageBasedMaterialPageRoute

_PageBasedMaterialPageRoute的作用是覆写了buildPage方法, 返回的是开发者写的界面;

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

官方为我们提供了默认的poppush动画,它们就在混入的MaterialRouteTransitionMixin中实现的。MaterialRouteTransitionMixin会根据不同的平台有不同的实现,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上?

先解释下其他参数:

  1. textDirection决定了滑动的方法,因为有些语言是从右到左排序的;
  2. transformHitTests设置为flase,点击事件的响应位置不受动画的影响;
  3. _primaryShadowAnimation是设置了一个动画中的阴影。

_secondaryPositionAnimation是从Offset.zeroOffset(-1.0/3.0, 0.0),正常情况下就是从右往左移动1/3的屏幕宽度。

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

_primaryPositionAnimation是从Offset(1.0, 0.0)Offset.zero,正常情况下就是从不可见的屏幕右边移动到屏幕最左边,然后占据整个屏幕宽度。

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

我们接下来解释下pop一个Route时候的动画逻辑, Animation:0->1

  1. 新加的Route是被_primaryPositionAnimation直接驱动的,也就是执行了从右到左的_kRightMiddleTween动画;
  2. _secondaryPositionAnimation只是被修改了值,我们前面TransitionRoute的介绍中提到过,新加入Routeanimation赋值给了前一个RoutesecondaryAnimation属性。_ModalScopeState中介绍过secondaryAnimation也能驱动Route的动画,也就是说前一个Route也能产生一个_kMiddleLeftTween动画;

概括:

新加的Route通过animation驱动从屏幕右边移动到左边的动画,animation赋值给了前一个RoutesecondaryAnimation驱动前一个Route向左移动1/3个屏幕位置。

push的逻辑类似,只是一个反向的动画reverse。前一个RoutesecondaryAnimation的驱动下右移了1/3屏幕宽度,当前的Routeanimation驱动下移出屏幕。

我们可以点击Flutter DevToolsSlow Animations看看动画的慢放过程:

Flutter系列十一:Flutter Navigator 2.0原理详解
动画
阶段总结
Flutter系列十一:Flutter Navigator 2.0原理详解
总结

_RouteEntry

Navigator不是直接操作的Route,而是Route的封装类_RouteEntry

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

_RouteEntry除了持有route外,还持有一个_RouteLifecycle,即路由状态。

函数则主要是修改_RouteLifecycle状态的函数,譬如markForPush,markForAdd,markForPop,markForRemove,markForComplete等。此外还有_RouteLifecycle被标记后对Route进行操作函数,譬如handlePushhandleAdd,handlePop,remove等。

Navigator

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

Navigator的构造方法中有一个关键的属性pages,Navigator会将传入的pages会转换成Routes对应的_RouteEntry数组。

实现申明式编程的逻辑就是修改这个pages中的内容,Navigator会自动实现对应的跳转,返回,替换等操作。Navigator.push,Navigator.pop等以前使用的方法就被将不是开发者需要考虑的使用方法了。

我们接下来分析NavigatorState的重要代码。

class NavigatorState extends State with TickerProviderStateMixin, RestorationMixin {
    
    List _history = [];
    
    late GlobalKey _overlayKey;
    OverlayState? get overlay => _overlayKey.currentState;
    
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
    
}
  1. _history就是pages中每个Page通过createRoute生成的_RouteEntry数组;
  2. OverlayStateoverlay代表的就是OverLay,它负责摆放每个RouteoverlayEntries数组;OverLay就相当于一个Stack,专门用于放置OverlayEntry

NavigatorState的核心方法是didUpdateWidget方法, 其调用了一个_updatePages()方法:

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

_updatePages方法的主要作用是对pages进行diff比对,更新_history数组中每个_routeEntry_RouteLifecycle, 最后调用_flushHistoryUpdates()方法。

_routeEntry比对的方法和MultiChildRenderObjectElement的比对方法是一样的,先前往后比对能复用的元素,然后从后往前比对能复用的元素,然后对剩下的元素进行复用或者新建,不能复用的元素进行销毁。

void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
    final List toBeDisposed = [];
    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. 根据每个_RouteEntry_RouteLifecycle调用对应的方法,例如如果Route被标记为_RouteLifecycle.push,则调用handlePush方法,这样此Route就会调用install方法插入Navigator的树中,然后进行动画;
  2. _flushObserverNotifications是对每个_NavigatorObservation监听者进行通知;
  3. _flushRouteAnnouncement主要是对每个Route的前后关系进行梳理更新,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 [],
              ),
            ),
          ),
        ),
      ),
    );
  }

顺便提一下HeroControllerScope是负责进行Hero动画的的Widget,类似于Android中的共享元素动画

阶段总结
Flutter系列十一:Flutter Navigator 2.0原理详解
阶段总结

到目前为止,我们通过切换Navigator的page就能够实现路由切换了,是不是文章就结束了?没有,因为Navigator 2.0是为Flutter 2.0 的全平台而生的,目前还没有解决一些问题,例如编辑浏览器网址网页返回安卓物理键返回等功能。

Router

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

我们看到Router有四个属性,RouteInformationProvider路由信息提供者,RouteInformationParser路由信息解析者,RouterDelegate路由信息的处理代理,BackButtonDispatcher返回处理的分发者。他们四个协同作用,共同实现路由的功能。

RouteInformation

上面说的到路由信息就是指RouteInformation,包括路由的路径location和路由对应的状态state。这里所指的状态就是数据。

class RouteInformation {

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

RouteInformationProvider只有一个抽象方法routerReportsNewRouteInformation,这个方法的作用是根据RouteInformation进行一些额外的操作。

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

系统默认使用的是PlatformRouteInformationProvider, 它的routerReportsNewRouteInformation方法中回调了系统路由的更新,例如浏览器就会在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路由信息进行相互转换:

abstract class RouteInformationParser {
  
  Future parseRouteInformation(RouteInformation routeInformation);

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

parseRouteInformation这个方法主要是解析初始路由的时候会使用到,例如 根据RouteInformation(location: "/")显示启动页面;

restoreRouteInformation这个方法就是根据T页面模型生成对应的RouteInformation

RouterDelegate

RouterDelegate顾名思义就是代替Router工作的类,它包括根据T页面模型添加一个页面,pop一个页面,提供构建的内容等。

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

  Future setNewRoutePath(T configuration);

  Future popRoute();

  T? get currentCOnfiguration=> null;

  Widget build(BuildContext context);
}

可以混入PopNavigatorRouterDelegateMixinpopRoute方法,就不用自己去实现了。

我们从源码角度看看RouteInformationProviderRouteInformationParserRouterDelegate他们三者在初始化路由是如何实现的:

class _RouterState extends State> {

  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(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
      .then(widget.routerDelegate.setInitialRoutePath)
      .then(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
      .then(_rebuild);
  }    
    
}

_processInitialRoute方法中我们看到了,routeInformationParser解析routeInformationProvidervalue,然后routerDelegate根据这个解析的结果去调用setNewRoutePath设置路由。

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

RouterDelegate的覆写案例:

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

    List get pages => List.unmodifiable(_pages);
        
    
    Future popRoute() {
        _removePage(_pages.last);
        return Future.value(false);
    }
    
    Future 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是状态管理的数据,用这个数据去驱动MyRouterDelegate的观察者也就是Router即去重构,这样Navigator也就会重构了。
  2. popRoute_pages的最后一个页面删掉,通知Router即去重构,更新Navigator
  3. setNewRoutePath_pages添加对应的Page,通知Router即去重构Navigator

BackButtonDispatcher

BackButtonDispatcher主要就是解决安卓,网页等物理返回的事件。它有两个子类RootBackButtonDispatcherChildBackButtonDispatcher可以解决Router的嵌套问题。

BackButtonDispatcher的返回处理可以直接交给RouterDelegate去处理,例如下面的逻辑:

class MyBackButtonDispatcher extends RootBackButtonDispatcher {

  final MyRouterDelegate _routerDelegate;

  MyBackButtonDispatcher(this._routerDelegate)
      : super();

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

}
最后总结
Flutter系列十一:Flutter Navigator 2.0原理详解
最后总结

总结

Navigator 2.0的功能更加强大了,使用方式也变得更加Flutter了。但是变得更复杂了,这样对学习和使用成本造成了很大的困扰,这方面也是很多人认为Navigator 2.0是一个失败的改造的原因。

本文主要从源码角度分析了Navigator 2.0的实现逻辑,原理清楚后写代码应该还是很简单的。

如果你需要Demo,可以参阅下面两篇文章的代码,特别是第一篇文章的代码非常具有参考价值:

Flutter Navigator 2.0 and Deep Links

Learning Flutter’s new navigation and routing system


推荐阅读
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
author-avatar
北极光的悲伤
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有