阅读 155

Flutter 路由源码剖析

博主相关文章列表

Flutter 框架实现原理

Flutter 框架层启动源码剖析

Flutter 页面更新流程剖析

Flutter 事件处理源码剖析

Flutter 路由源码剖析

Flutter 安卓平台源码剖析

Flutter 自定义控件之RenderObject

Flutter 路由源码剖析

路由页面的简单树形结构,仅包含关键控件

根路由初始化流程

我们知道MaterialApp实际上是对WidgetsApp的包装,而WidgetsApp是一个有状态的Widget,这里查看它的State.build实现,主要是构建了Navigator

flutter\lib\src\widgets\app.dart

/// [_WidgetsAppState]

Widget build(BuildContext context) {
  Widget navigator;
  if (_navigator != null) {
    // 构建 Navigator
    navigator = Navigator(
      key: _navigator,
      // 如果window.defaultRouteName不是'/',我们应该假设它是通过`setInitialRoute`有意设置的,
      // 并且应该覆盖[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,
    );
  }

  Widget result;
  if (widget.builder != null) {
    result = Builder(
      builder: (BuildContext context) {
        return widget.builder(context, navigator);
      },
    );
  } else {
    assert(navigator != null);
    result = navigator;
  }

  if (widget.textStyle != null) {
    result = DefaultTextStyle(
      style: widget.textStyle,
      child: result,
    );
  }

  PerformanceOverlay performanceOverlay;
  // 如果设置了任何显示或checkerboarding选项,则需要推送性能叠加层
  if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) {
    performanceOverlay = PerformanceOverlay.allEnabled(
      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
    );
  } else if (widget.checkerboardRasterCacheImages || widget.checkerboardOffscreenLayers) {
    performanceOverlay = PerformanceOverlay(
      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
    );
  }
  if (performanceOverlay != null) {
    result = Stack(
      children: <Widget>[
        result,
        Positioned(top: 0.0, left: 0.0, right: 0.0, child: performanceOverlay),
      ],
    );
  }

  if (widget.showSemanticsDebugger) {
    result = SemanticsDebugger(
      child: result,
    );
  }


  Widget title;
  if (widget.onGenerateTitle != null) {
    title = Builder(
      // 该Builder的存在是为了在Localizations小部件下面提供上下文
      // onGenerateTitle回调可以通过其context参数引用Localizations
      builder: (BuildContext context) {
        final String title = widget.onGenerateTitle(context);
        return Title(
          title: title,
          color: widget.color,
          child: result,
        );
      },
    );
  } else {
    title = Title(
      title: widget.title,
      color: widget.color,
      child: result,
    );
  }

  final Locale appLocale = widget.locale != null
    ? _resolveLocales(<Locale>[widget.locale], widget.supportedLocales)
    : _locale;

  return Shortcuts(
    shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
    debugLabel: '<Default WidgetsApp Shortcuts>',
    child: Actions(
      actions: widget.actions ?? WidgetsApp.defaultActions,
      child: FocusTraversalGroup(
        policy: ReadingOrderTraversalPolicy(),
        child: _MediaQueryFromWindow(
          child: Localizations(
            locale: appLocale,
            delegates: _localizationsDelegates.toList(),
            child: title,
          ),
        ),
      ),
    ),
  );
}
复制代码

Navigator也是有状态Widget,这里查看NavigatorState实现

flutter\lib\src\widgets\navigator.dart

/// [NavigatorState]

List<_RouteEntry> _history = <_RouteEntry>[];

void initState() {
    super.initState();

    for (final NavigatorObserver observer in widget.observers) {
        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 {
        // 如果没有提供页面,我们将需要提供默认路由来初始化导航器
        initialRoute = initialRoute ?? Navigator.defaultRouteName;
    }
    if (initialRoute != null) {
        // 此处创建根路由,并添加到_history中保存
        _history.addAll(
            widget.onGenerateInitialRoutes(
                this,
                widget.initialRoute ?? Navigator.defaultRouteName
            ).map((Route<dynamic> route) =>
                  _RouteEntry(
                      route,
                      initialState: _RouteLifecycle.add,
                  ),
                 ),
        );
    }
    // 刷新路由栈
    _flushHistoryUpdates();
}

void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
    // 清理列表,给改变的路由发送更新
    int index = _history.length - 1;
    _RouteEntry next;
    _RouteEntry entry = _history[index];
    _RouteEntry previous = index > 0 ? _history[index - 1] : null;
    bool canRemoveOrAdd = false; // 是否在上面有一个完全不透明的路由,在下面悄悄地删除或添加路由
    Route<dynamic> poppedRoute; // 应该在最上面的活动路由上触发didPopNext的路由
    bool seenTopActiveRoute = false; // 我们是否已经看过将获得didPopNext的路由
    final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
    while (index >= 0) {
        switch (entry.currentState) {
            case _RouteLifecycle.add:
                entry.handleAdd(
                    navigator: this,
                );
                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
                    );
                    continue;
                }
                break;
            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;
            case _RouteLifecycle.pushing: // 动画完成时将退出此状态。
                if (!seenTopActiveRoute && poppedRoute != null)
                    entry.handleDidPopNext(poppedRoute);
                seenTopActiveRoute = true;
                break;
            case _RouteLifecycle.idle:
                if (!seenTopActiveRoute && poppedRoute != null)
                    entry.handleDidPopNext(poppedRoute);
                seenTopActiveRoute = true;
                // 该路由是空闲的,因此我们可以删除等待静默删除的后续(较早)路由:
                canRemoveOrAdd = true;
                break;
            case _RouteLifecycle.pop:
                if (!seenTopActiveRoute) {
                    if (poppedRoute != null)
                        entry.handleDidPopNext(poppedRoute);
                    poppedRoute = entry.route;
                }
                entry.handlePop(
                    navigator: this,
                    previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
                );
                canRemoveOrAdd = true;
                break;
            case _RouteLifecycle.popping:
                // 动画完成时将退出此状态
                break;
            case _RouteLifecycle.remove:
                if (!seenTopActiveRoute) {
                    if (poppedRoute != null)
                        entry.route.didPopNext(poppedRoute);
                    poppedRoute = null;
                }
                entry.handleRemoval(
                    navigator: this,
                    previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
                );
                continue;
            case _RouteLifecycle.removing:
                if (!canRemoveOrAdd && next != null) {
                    // 我们还不允许删除此路由
                    break;
                }
                entry.currentState = _RouteLifecycle.dispose;
                continue;
            case _RouteLifecycle.dispose:
                // 延迟处理,直到didChangeNext/idChangePrevious被发送
                toBeDisposed.add(_history.removeAt(index));
                entry = next;
                break;
            case _RouteLifecycle.disposed:
            case _RouteLifecycle.staging:
                assert(false);
                break;
        }
        index -= 1;
        next = entry;
        entry = previous;
        previous = index > 0 ? _history[index - 1] : null;
    }
    // 现在列表很干净了,发送didChangeNext / didChangePrevious通知
    _flushRouteAnnouncement();

    // 宣布更改路由名称。
    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;
    }

    // 最后,删除所有被标记的overlay实体对象并调用dispose。
    for (final _RouteEntry entry in toBeDisposed) {
        for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
            overlayEntry.remove();
        entry.dispose();
    }
    if (rearrangeOverlay)
        overlay?.rearrange(_allRouteOverlayEntries);
}


Widget build(BuildContext context) {
  return Listener(
    onPointerDown: _handlePointerDown,
    onPointerUp: _handlePointerUpOrCancel,
    onPointerCancel: _handlePointerUpOrCancel,
    child: AbsorbPointer(
      absorbing: false, // 它直接被上面的_cancelActivePointers方法修改
      child: FocusScope(
        node: focusScopeNode,
        autofocus: true,
        child: Overlay(
          key: _overlayKey,
          initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
        ),
      ),
    ),
  );
}

// 通过生成器迭代_history列表,返回根路由包含的OverlayEntry对象
Iterable<OverlayEntry> get _allRouteOverlayEntries sync* {
    for (final _RouteEntry entry in _history)
        yield* entry.route.overlayEntries;
}
复制代码

可以看到,较为复杂的是_flushHistoryUpdates方法,该方法中根据不同的路由生命期做了相应处理。_RouteLifecycle是一个枚举:

enum _RouteLifecycle {
  staging, // 我们将等待transition delegate决定如何处理此路由
  //
  // 存在的路由:
  //
  add, // 我们要运行install、didAdd等;由onGenerateInitialRoutes或初始widget.pages创建的路由。
  adding, // 同上
  // 准备好过渡的路由。
  push, // 我们将要运行install,didPush等; 通过push()和朋友添加的路线
  pushReplace, 
  pushing, // 我们正在等待didPush的future完成
  replace, // 
  idle, // 路由是无害的
  //
  // 不存在的路由:
  //
  // 应该包含在路由公告中的路由,并且仍应侦听过渡变化
  pop, // 我们要调用didPop
  remove, // 我们要运行didReplace/didRemove等操作
  // 路不应包含在路公告中,但仍应监听过渡变化。
  popping, // 我们正在等待路由调用finalizeRoute来切换处理
  removing, // 我们正在等待后续的路由完成动画制作,然后再切换到dispose
  // 已从navigator和overlay中完全删除的路由
  dispose, // 我们会在短时间内处理好此路由
  disposed, // 我们已经处理好了路由
}
复制代码

官方给出了一个 _RouteLifecycle状态机(仅向下)的图示:

                   [创建一个_RouteEntry]
                             |
                             +
                             |\
                             | \
                             | staging
                             | /
                             |/
                +-+----------+--+-------+
               /  |             |       |
              /   |             |       |
             /    |             |       |
            /     |             |       |
           /      |             |       |
  pushReplace   push*         add*   replace*
           \       |            |       |
            \      |            |      /
             +--pushing#      adding  /
                      \        /     /
                       \      /     /
                       idle--+-----+
                       /  \
                      /    \
                    pop*  remove*
                    /        \
                   /       removing#
                 popping#       |
                  |             |
               [finalizeRoute]  |
                          \     |
                          dispose*
                             |
                             |
                          disposed
                             |
                             |
                   [_RouteEntry垃圾回收]
                         (终端状态)
复制代码

* :表示这些状态是短暂的;一旦运行_flushHistoryUpdates,路由条目就会退出该状态。

# :表示这些状态等待future或其他事件,然后自动转换。

此处初始化根路由时,设置的状态是_RouteLifecycle.add,因此执行_RouteEntryhandleAdd方法

void handleAdd({ @required NavigatorState navigator}) {
  route._navigator = navigator;
  route.install();
  currentState = _RouteLifecycle.adding;
}
复制代码

这里最主要的是调用了路由的install()方法,将根路由转换为OverlayEntry对象,并插入到Overlay管理的页面栈中

/// [OverlayRoute]

void install() {
  _overlayEntries.addAll(createOverlayEntries());
  super.install();
}
复制代码
/// [ModalRoute]

// 这里直接返回了一个Iterable,它包含两个OverlayEntry对象,被添加到了_overlayEntries中
Iterable<OverlayEntry> createOverlayEntries() sync* {
  // 这是一个模态屏障控件,阻止用户与自己身后的控件交互(即拦截了事件传递)
  yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
  // 为根路由创建 OverlayEntry
  yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
复制代码

NavigatorState中的build方法则主要构建了Overlay控件,继续查看OverlayState实现逻辑

flutter\lib\src\widgets\overlay.dart

/// [OverlayState]

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

void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
    if (entries.isEmpty)
        return;
    for (final OverlayEntry entry in entries) {
        entry._overlay = this;
    }
    setState(() {
        _entries.insertAll(_insertionIndex(below, above), entries);
    });
}

Widget build(BuildContext context) {
  // 这个列表是倒着填的,然后在下面倒过来再加到树上
  final List<Widget> children = <Widget>[];
  bool onstage = true;
  int onstageCount = 0;
  for (int i = _entries.length - 1; i >= 0; i -= 1) {
    final OverlayEntry entry = _entries[i];
    if (onstage) {
      onstageCount += 1;
      children.add(_OverlayEntryWidget(
        key: entry._key,
        entry: entry,
      ));
      if (entry.opaque)
        onstage = false;
    } else if (entry.maintainState) {
      children.add(_OverlayEntryWidget(
        key: entry._key,
        entry: entry,
        tickerEnabled: false,
      ));
    }
  }
  return _Theatre(
    skipCount: children.length - onstageCount,
    children: children.reversed.toList(growable: false),
  );
}
复制代码

build方法中可知,这里遍历OverlayEntry列表,将保存的实体信息对象封装为_OverlayEntryWidget控件,最终将包含_OverlayEntryWidget的列表交给_Theatre控件插入控件树中用于渲染。

这里_Theatre控件是一个特殊版本的Stack,它不对第一个skipCount子级进行布局和渲染。第一个skipCount子级被认为是 offstage

_Theatre控件的名称很有意思,翻译过来是剧院的意思,它将页面分为了两种,一种是舞台上的(onstage)演员,另一种则是舞台下的(offstage)观众。当某个包装页面的OverlayEntryopaque属性为true时,表示占满全屏且不透明,那么以它为分界线,它之下的所有页面都不需要绘制了(因为被挡住了看不见)。如果OverlayEntrymaintainState属性也为true,则被分到舞台下的观众那一组,否则,连进入剧院的资格也没有。

以上是根路由的整个初始化流程,包含路由的创建,路由入栈,路由转化等,需要注意的是,这里涉及的栈有两个,一个是保存路由的栈,一个是保存页面的栈,分别是

  • NavigatorState中的List<_RouteEntry> _history = <_RouteEntry>[],保存路由
  • OverlayState中的List<OverlayEntry> _entries = <OverlayEntry>[],保存用于构建页面Widget的数据结构

路由栈操作剖析

虽然Navigator包含一些静态方法操作路由栈,但最终调用的是仍然是NavigatorState中的操作方法。这就是为什么需要传一个context的原因,Navigator需要从上下文中查找一个NavigatorState。这里我们直接查看NavigatorState中的push实现

/// [NavigatorState]

Future<T> push<T extends Object>(Route<T> route) {
   // 使用_RouteEntry包装传进来的路由然后入栈,并将路由状态设为_RouteLifecycle.push
  _history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
   // 刷新路由栈
  _flushHistoryUpdates();
   // 用于一些调试,在非release模式下执行
  _afterNavigation(route);
  return route.popped;
}
复制代码

_flushHistoryUpdates方法上面已经剖析过,此处进入case_RouteLifecycle.push的分支,执行_RouteEntryhandlePush方法

void handlePush({ @required NavigatorState navigator, @required bool isNewFirst, @required Route<dynamic> previous, @required Route<dynamic> previousPresent }) {

  final _RouteLifecycle previousState = currentState;
  route._navigator = navigator;
  // 调用路由的install方法进行转换
  route.install();

  if (currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace) {
    // 推送转换完成后,返回的值将解析
    final TickerFuture routeFuture = route.didPush();
    // 置为 pushing状态
    currentState = _RouteLifecycle.pushing;
    routeFuture.whenCompleteOrCancel(() {
      if (currentState == _RouteLifecycle.pushing) {
        // 置为 idle状态
        currentState = _RouteLifecycle.idle;
        // 以idle状态再次进入路由栈更新方法
        navigator._flushHistoryUpdates();
      }
    });
  } else {
    route.didReplace(previous);
    currentState = _RouteLifecycle.idle;
  }
  if (isNewFirst) {
    route.didChangeNext(null);
  }

  if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
    for (final NavigatorObserver observer in navigator.widget.observers)
      observer.didReplace(newRoute: route, oldRoute: previous);
  } else {
    // 遍历路由监听器,回调 didPush方法
    for (final NavigatorObserver observer in navigator.widget.observers)
      observer.didPush(route, previousPresent);
  }
}
复制代码

poppush的流程类似

void pop<T extends Object>([ T result ]) {
  final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);
  if (entry.hasPage) {
    if (widget.onPopPage(entry.route, result))
      entry.currentState = _RouteLifecycle.pop;
  } else {
    // 修改路由状态为_RouteLifecycle.pop
    entry.pop<T>(result);
  }
  if (entry.currentState == _RouteLifecycle.pop) {
    // 如果路由真的要被弹出(弹出不是内部处理的),则刷新路由栈
    _flushHistoryUpdates(rearrangeOverlay: false);
  }
  _afterNavigation<dynamic>(entry.route);
}
复制代码

相关类总结

Navigator

导航器,实际上它是一个用堆栈规则来管理子控件的一个控件。结合前面的知识,它主要用于管理我们的路由栈。

移动应用程序通常通过全屏元素显示其内容,称为 "屏幕 "或 "页面"。在Flutter中,这些元素被称为路由,它们由Navigator管理。导航器管理Route对象的堆栈,并提供了两种管理堆栈的方式,

  • 声明式API: Navigator.pages
  • 命令式API: Navigator.pushNavigator.pop

如果提供了Navigator.pagesNavigator将把它的Navigator.pages转换为Routes的堆栈。Navigator.pages 的变化将触发路由堆栈的更新。Navigator将更新它的Routes以匹配Navigator.pages的新配置。要使用这个API,可以使用CustomBuilderPage或创建一个Page子类,并为Navigator.pages定义一个Pages列表。此外,还需要一个Navigator.onPopPage回调,以便在弹出时正确清理输入页面。

显然的,在WidgetsApp的封装中,并未使用这种声明式API的方式管理页面栈。

Navigator 类中,持有一个路由监听器NavigatorObserver的列表,用于在适当的时候回调监听器,通知当前路由栈的操作状态。在NavigatorState中,则持有一个List<_RouteEntry>类型的_history列表,存放的是经过封装的历史路由页面。_RouteEntry是对路由的包装类,可以为TransitionDelegate暂存,以决定其基础Route如何在屏幕上或屏幕外过渡。

还有一个重点需要关注,在NavigatorState中持有一个OverlayState的引用。

OverlayState get overlay => _overlayKey.currentState;
复制代码

NavigatorStatebuild方法中创建了一个Overlay,它是一个有状态Widget,还包含一个OverlayState,那么这个Overlay及其OverlayState有什么用呢?

查看 UML大图

UML类图

Overlay

Overlay可以翻译为叠加层或覆盖层。按照官方的解释,它是一个可以独立管理的覆盖层堆栈。简单说,它才是我们页面栈的真正实现。它的内部持有一个List<OverlayEntry>类型的initialEntries列表,但是这个列表仅包含页面栈初始化时所包含的页面,只是在OverlayState被初始化时使用,见OverlayStateinitState方法,而真正的页面栈储存在OverlayState_entries列表中。

final List<OverlayEntry> _entries = <OverlayEntry>[];
复制代码

OverlayState中,仅暴露了三个可用于修改页面栈的方法

到这里,相信大家已经彻底明白在Flutter中使用Overlay去显示悬浮框的原理,我们使用 Overlay.of()查找到当前Navigator持有的OverlayState对象, 然后在页面栈中插入一层Widget显示到栈顶部。

查看 UML大图

UML类图

OverlayEntry

OverlayEntry只是一个普通类,或者说是一个数据结构,是对需要显示的页面Widget的一个实体数据包装。

默认情况下,如果在这个实体对象上层有一个完全不透明的实体对象,那么这个实体对象所代表的Widget将不会被包含在控件树中(特别是,实体对象中包装的有状态Widget将不会被实例化)。要确保实体对象包装的Widget即使不可见也仍然被构建,需要将 maintainState 设置为 true。这是比较耗费性能的,应谨慎操作。特别是,如果实体对象包装的Widget在 maintainState 设置为 true 的情况下还反复调用 State.setState,将会增加手机电量的消耗。

OverlayEntry的两个关键属性

  • maintainState

    这个实体对象包装的Widget是否必须包含在控件树中,即使在它上面有一个完全不透明的实体。NavigatorRoute对象使用它来确保即使在后台也保留路由,这样当后续路由完成时,来自后续路由的Future将被正确处理。

  • opaque

    是否是全屏不透明。如果某个实体对象声称是不透明的,则出于效率考虑,除非设置了maintainState为ture,否则叠加层会跳过位于该实体对象下面的所有实体对象。既然是不透明的,那么它下面的页面也看不见,于是干脆就不去绘制了。

    我们知道,对话框本身也是路由,但通常不会占满全屏,而是四周呈现半透明效果,它就是一种opaque为false的情况。

Route

这里Route作为一个抽象的基类,下面的每一层路由类都处理了相关的逻辑:

OverlayRoute:在导航器的Overlay中显示控件的路由。主要将路由转换为Widget插入控件树。

TransitionRoute:具有进入和退出过渡动画的路由。主要处理路由过渡动效。

ModalRoute:阻止与下层路由交互的路由。它覆盖整个导航器。但它们不一定是不透明的。例如一个对话框。主要处理事件的拦截

PageRoute:替换整个屏幕的模态路由。由它派生出了我们熟悉的MaterialPageRoute,主要用于Flutter的页面切换。

PopupRoute:在当前路由上覆盖Widget的模态路由。主要用于弹出框,对话框之类。

查看 Route UML大图

自定义悬浮框路由

Flutter中使用Overlay操作页面栈可以插入悬浮框,通过Navigator 操作路由栈可以创建对话框或弹出框。但有时候普通对话框并不能满足我们的需求,这是因为对话框由两层构成,背后还有一层蒙层,拦截事件的传递,而Overlay悬浮框控制起来较为麻烦,不能响应返回键退栈,且永远在最顶层,无法被新页面覆盖。

此时,结合我们前面研究路由源码的心得,我们完全可以自定义路由,实现我们想要的效果。譬如,我们不希望有一个背景蒙层阻断事件,我们需要的只是一个小巧的悬浮控件,而不是覆盖全屏,这时可以自定义类继承OverlayRoute,因为只有继承OverlayRoute或者TransitionRoute才能跳过背景蒙层的处理逻辑,为了简单,不想处理动画相关问题,直接使用OverlayRoute

class MyPopupRoute extends OverlayRoute{

  // 实现该方法将路由转化为OverlayEntry对象返回
  @override
  Iterable<OverlayEntry> createOverlayEntries() sync*{
    yield OverlayEntry(builder: (ctx){
      return Material(
        child: Align(
          alignment: Alignment.center,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      );
    });
  }
}
复制代码

弹出悬浮控件,会在屏幕中心显示一个蓝色正方形,返回键可消除,新启页面亦能覆盖悬浮框,悬浮框下层页面中的按钮亦能响应点击。

onTap: (){
  Navigator.of(context).push(MyPopupRoute());
}
复制代码

通过自定义路由,我们能封装出各种想要的悬浮框或对话框控件,大大提高了开发的灵活度。

视频课程

本篇博客视频内容可访问B站链接 路由源码剖析及自定义 您觉得有帮助,别忘了点赞哦

如需要获取完整的Flutter全栈式开发课程,请访问以下地址 Flutter全栈式开发之Dart 编程指南 二维码

Flutter 全栈式开发指南