Flutter体验 Day 18-路由导览v2

路由导览v2

Navigator 2.0

Flutter Navigator 2.0 使用宣告式(declarative style)的方式定义路由导览的方式,需要事先具备基本概念:

  • Page:用来设定路由栈的抽象类别
  • Router:使用Navigator透过RouterDelegatepages设定显示对应画面
  • RouteInformationParser:使用RouteInformation路由资讯解析成自定义的路由设定类别(T)
  • RouteInfomationProvider:提供路由资讯RouteInformation的来源
  • RouterDelegate:监听RouteInformationParser提供的路由设定管理pages的内容
  • BackButtonDispatcher:用通知Router 点击返回按钮的事件
  • TransitionDelegate:用来定义屏幕新增或移除的的转换行为

UML 类别图如下

Navigator

引用:Flutter Navigator 2.0 and Deep Links (Kevin D Moore, Feb 23 2021)
www.raywenderlich.com

起步~走

我们需要实作 RouterDelegateRouteInformationParser 这两个抽象类别。

class MyApp extends StatefulWidget {
  @override
  _MyApp createState() => _MyApp();
}

class _MyApp extends State<MyApp> {
  AppRouterDelegate _delegate = AppRouterDelegate();
  AppRouterParser _parser = AppRouterParser();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: "Navigator v2",
      routerDelegate: _delegate,
      routeInformationParser: _parser,
    );
  }
}

App在启动的时候,会使用 RouteInformationParser 解析要处理的路径,在parseRouteInformation方法里我们会收到 RouteInfomationProvider 提供的 RouteInformation

  @override
  Future<RoutePage> parseRouteInformation(RouteInformation routeInformation) {
    print(
        'RouteInformationParser parseRouteInformation ${routeInformation.location}');

    RoutePage configuration =
        getRoutePage(name: routeInformation.location ?? '/');

    return SynchronousFuture(configuration);
  }

  @override
  RouteInformation restoreRouteInformation(RoutePage configuration) {
    print(
        'RouteInformationParser restoreRouteInformation ${configuration.path}');
    return RouteInformation(location: configuration.path ?? '/');
  }

其中泛型<T>可以自行定义想要的类别(RoutePage),这边简单的使用一个函数处理对应的路由设定并绑定 widget 内容。

class RoutePage extends RouteSettings {
  final Widget? widget;

  const RoutePage({
    required String name,
    Object? arguments,
    this.widget,
  }) : super(name: name, arguments: arguments);
}

RoutePage getRoutePage({required String name}) {
  print("getRoutePage $name");
  switch (name) {
    case '/':
      return RoutePage(name: name, widget: HomePage());
    case '/items':
      return RoutePage(name: name, widget: ItemsPage());
    default:
      return RoutePage(name: '404', widget: UnknownPage());
  }
}

接着宣告 RouterDelegate 必要的属性 pages

class AppRouterDelegate extends RouterDelegate<RoutePage>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePage> {
  final List<Page> _pages = [];

  @override
  final GlobalKey<NavigatorState> navigatorKey;
  ...
}

定义setNewRoutePath行为:RouteInformationParser 解析完路由会接着呼叫setNewRoutePath方法并带入自定义的路由设定泛型(RoutePage)。

我们可以在这边定义收到新的路由设定时要如何操作pages的内容。

  @override
  Future<void> setNewRoutePath(RoutePage configuration) {
    print("RouterDelegate setNewRoutePath ${configuration.name}");

    final shouldAddPage = _pages.isEmpty ||
        (_pages.last.arguments as RoutePage).name != configuration.name;

    if (shouldAddPage) {
      replace(configuration);
    }

    return SynchronousFuture(null);
  }

RouterDelegate 会透过 build 方法重构 NavigatorNavigator 提供了底层路由的架构,Navigator 在透过 Overlaypages 的内容渲染在画面上。

  @override
  Widget build(BuildContext context) {
    print("RouterDelegate build");
    return Navigator(
      key: navigatorKey,
      onPopPage: _onPopPage,
      pages: List.of(_pages),
    );
  }

还需要覆写 RouterDelegatecurrentConfiguration,在呼叫完 build 後,程序会呼叫RouteInformationParserrestoreRouteInformation,用来将路由设定写回记录(可参考 browser history 的行为)。

  @override
  RoutePage get currentConfiguration {
    print("RouterDelegate currentConfiguration");
    return _pages.last.arguments as RoutePage;
  }

操作 pages

RouterDelegate 管理 pages 来处理路由的状态。 pages 只能是 Page 抽像类别的子类别,可以实作相关设定客制化想要的行为,例如过场特效。

对於 pages 异动後,需要呼叫 notifyListeners 通知 Router 重新整理。

  replace(RoutePage configuration) {
    _pages.clear();
    _pages.add(
      MaterialPage(
        child: configuration.widget,
        key: ValueKey(configuration.name),
        name: configuration.name,
        arguments: configuration,
      ),
    );
    notifyListeners();
  }

可以比对主控台的讯息,理解呼叫的先後顺序。

Restarted application in 166ms.
RouteInformationParser parseRouteInformation /
[RoutePage] getRoutePage /
RouterDelegate setNewRoutePath Home
RouterDelegate build
RouterDelegate currentConfiguration
RouteInformationParser restoreRouteInformation /

RouterDelegate 静态方法 - of

为了让 Navigator 下的子元素可以存取路由的设定,我们可以透过 Router.of 的方式找到父层中的的 delegate

  static AppRouterDelegate of(BuildContext context) {
    RouterDelegate routerDelegate = Router.of(context).routerDelegate;
    return routerDelegate as AppRouterDelegate;
  }

使用方式如下:

AppRouterDelegate.of(context).pushNamed('/items');

今日成果

练习成果

范例中使用两个画面测试自己定义的 replacepush 方法,这些方法都只是对 pages 进行操作而已。

widget_nav2


<<:  Day26 - 轻前端 Component - jQuery UI Selectmenu

>>:  Day11-Database——效能的储备足够吗?-N+1 query

Day19:别说那麽多废话,讲重点

Lambda在刚开始学Java一定会很不想碰,会觉得好不容易对Java有点熟悉了,结果又搞出一整陀新...

mostly:functional 谢幕与片尾曲

The future is already here – it's just not evenly...

Day27:歪楼+卡文(全英文笔记 - I)

铁人赛写到现在,其实主题中的内容还有很多可以实作的部分,但一方面是不确定时间到期後,铁人赛系列还能不...

Day4简单实例练习

今天要介绍透过CDN连结的方式快速运用react,简单建立一个react component。 CD...

VM 执行个体 (二)

GCE 昨天有说到了执行个体建立,那如果有需求将相同服务性质绑在同一个群组GCP上是否可以做得到,没...