iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Mobile Development

Flutter / Dart 跨平台App開發體驗系列 第 18

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


上一篇
Flutter體驗 Day 17-路由導覽v1
下一篇
Flutter體驗 Day 19-InheritedWidget
系列文
Flutter / Dart 跨平台App開發體驗30

尚未有邦友留言

立即登入留言