Flutter 路由我定

发布时间:2024-01-06 23:00:30

相关阅读

基于 Navigator1.0 :

前言

Flutter 1.22?发布带来了 Navigator2.0 , 给了开发者更多选择,你可以灵活地管理路由栈,可以处理在浏览器里面输入的情况,也可以嵌套多个?Navigator,虽然仍然有缺点,但是基本上可以做到?路由我定。下面跟我一起走进 Flutter 路由的世界。本文源码版本为?Flutter Stable 1.22.6

路由基础

Navigator

负责整个路由栈, 在结构上面其实是一个?Overlay?有点类似?Stack,大家经常用它来做?toast,比如?oktoast,其实每个页面就是一个?OverlayEntry.

RouteSettings

用来保存路由名字和参数

Page

Navigator2.0 中出现,继承于?RouteSettings?。主要负责自己创建?Route?以及提一个 key,这个 key 是后面?Page变化判断的依据,注意这个 key 在通常情况下应该为唯一的 key 。

Route

主要负责处理跳转的动画,保存?RouteSettings?(即路由的名字和参数)。也是 OverlayEntry,Navigator,_RouteEntry 的纽带。

  • 在?push?方法中,Route?直接传递进来,赋值给?_RouteEntry?增加到?_history?中
  Future<T> push<T extends Object>(Route<T> route) {
    _history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
    _flushHistoryUpdates();
    _afterNavigation(route);
    return route.popped;
  }
  • NavigatorState.build?中 Overlay 的 initialEntries 的值等于 _history 中全部 _RouteEntry.route 的 OverlayEntry
  Iterable<OverlayEntry> get _allRouteOverlayEntries sync* {
    for (final _RouteEntry entry in _history)
      yield* entry.route.overlayEntries;
  }
 
 Overlay(
   key: _overlayKey,
    initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
   ),

RouteTransitionRecord?/?_RouteEntry

后者继承前者,记录每个路由的状态

RouteTransitionRecord?中有以下属性和方法,它们都在?TransitionDelegate.resolve?方法中设置

isWaitingForEnteringDecision?标记路由是否等待进入屏幕
isWaitingForExitingDecision?标记路由是否等待离开屏幕

方法进出动画返回参数
markForPush()N/A
markForAdd()N/A
markForPop([dynamic result])
markForComplete([dynamic result])
markForRemove()

TransitionDelegate?/?DefaultTransitionDelegate

RouteTransitionRecord?在?TransitionDelegate.resolve?中进行设置.

1.当?Pages?变化的时候触发更新,执行了?NavigatorState.didUpdateWidget
2.在这个方法中,去对比了新旧?Pages?(我们前面说的 K 新增了一个 key,就在这里发挥了作用)
3.resolve?中判断哪些是新增,哪些是要移除的。

DefaultTransitionDelegate 中的大概逻辑如下

新Pages旧Pages状态
A=>BA=>B=>CC markForPop
A=>CA=>B=>CB markForComplete
A=>B=>C=>DA=>B=>CD markForPush
A=>B=>D=>CA=>B=>CD markForAdd

NavigatorObserver

用于监控 push,pop,replace,remove 路由的情况,以及 ios 平台上面左滑退出页面。通常我们可以在这个里面做页面进出埋点,以及解决混合开发中 Flutter与原生 ios 左滑退出冲突。

class NavigatorObserver {
  NavigatorState get navigator => _navigator;
  NavigatorState _navigator;
  
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didReplace({ Route<dynamic> newRoute, Route<dynamic> oldRoute }) { }

  void didStartUserGesture(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didStopUserGesture() { }
}

Navigator 1.0

这个大家都用了很久了,相信都是非常了解的了,推荐使用命名路由,并且在?onGenerateRoute?回调中统一管理路由。之前法法路由注解也是基于??Navigator 1.0?的,不懂的可以去回顾下。

Navigator 2.0

先看一下?Navigator?的最新构造有了一些什么变化。

  const Navigator({
    Key key,
    // Navigator 2.0 的新东西,由它间接把路由栈暴露给用户
    this.pages = const <Page<dynamic>>[],
    // 当使用代码 pop 或者按浏览器后退按钮的时候的回调,这个时候用户可以自行处理逻辑
    this.onPopPage,
    this.initialRoute,
    this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
    this.onGenerateRoute,
    this.onUnknownRoute,
    // 由于 pages 是暴露给用户了,所以这里可以自己设置页面的过度动画状态,见上面 [TransitionDelegate] 部分。一般来说直接用默认的就好了
    this.transitionDelegate = const DefaultTransitionDelegate<dynamic>(),
    // 是否通知给引擎,主要是在 Web 上面,告诉浏览器 URL 的改变,同步地址
    this.reportsRouteUpdateToEngine = false,
    this.observers = const <NavigatorObserver>[],
  }) 

Navigator 栗子

  1. 我们准备一下?Page,简单的实现下?createRoute?方法
class MyPage extends Page<void> {
  const MyPage({
    @required LocalKey key,
    @required String name,
    @required this.widget,
    Object arguments,
  }) : super(
          key: key,
          name: name,
          arguments: arguments,
        );

  final Widget widget;
  @override
  Route<void> createRoute(BuildContext context) {
    return MaterialPageRoute<void>(
      settings: this,
      builder: (BuildContext context) => widget,
    );
  }
}
  1. 准备?pages,这就是我们的路由栈,初始化的一个 MainPage。要注意的是,key 必须是一个 唯一的 key。
  final List<MyPage> _pages = <MyPage>[
    MyPage(
        name: 'MainPage', widget: const TestPage('MainPage'), key: UniqueKey()),
  ];
  1. 使用 Navigator,值得注意的是
  • pages?应该是一个新的集合,这样在?NavigatorState.didUpdateWidget?中才会判断不同,并且更新
  • onPopPage?回调可以根据自身的情况,进行操作,最后?setState,通知?pages?变化。通过调用?navigatorKey.currentState.pop()?或者 点击 Appbar 返回按钮都会触发该回调
    Navigator(
      reportsRouteUpdateToEngine: true,
      key: navigatorKey,
      pages: _pages.toList(),
      onPopPage: (Route<dynamic> route, dynamic result) {
        if (_pages.length > 1) {
          _pages.removeLast();
          setState(() {});
          return route.didPop(result);
        }
        return false;
      },
    ), 
  1. 现在你就可以任意去操作路由栈了,下面举几个例子。

新增一个页面,相当于?push

    _pages.add(
      MyPage(
          name: 'MainPageA',
          widget: const TestPage('MainPageA'),
          key: UniqueKey()),
    );
    setState(() {});

移除最后一个,相当于?pop

    if (_pages.length > 1) {
      _pages.removeLast();
      setState(() {});
    }

直接使用?NavigatorState.pop()?方法,触发?onPopPage?回调

    navigatorKey.currentState.pop();    

现在看起来,我们能够完美控制整个路由栈了,是不是就够了呢? 答案肯定是不够的,我们还没有处理?浏览器输入修改URL,?浏览器返回键安卓物理返回键,以及?Navigator 嵌套的问题。

Router

Navigator 2.0 的新东西,一眼看过去全是新东西。

  const Router({
    Key key,
    this.routeInformationProvider,
    this.routeInformationParser,
    @required this.routerDelegate,
    this.backButtonDispatcher,
  })

RouteInformation

存在下面2种场景:

  1. RouteInformationProvider => Router , 这种情况发生在有新的路由可用,比如在浏览器中输入一个新URL,或者在代码设置初始化路由。
  2. Router => RouteInformationProvider, 这种情况只发生在通知引擎改变浏览器 URL。
class RouteInformation {

  const RouteInformation({this.location, this.state});
  /// 比如: `/`, `/path`, `/path/to/the/app`.
  final String location;

  /// 当前页面的状态,比如滚动位置,必须是可以序列化的对象.
  final Object state;
}

RouteInformationParser

主要负责解析?RouteInformation,这里的?T?一般为?String?或者?RouteSettings,方便我们进行解析。

abstract class RouteInformationParser<T> {

  const RouteInformationParser();
  /// 浏览器中输入一个新URL,或者在代码设置初始化路由
  Future<T> parseRouteInformation(RouteInformation routeInformation);
  /// 注意如果 reportsRouteUpdateToEngine 设置为true了,这个必须实现,不能返回 null。
  /// 传入的 T 从 RouterDelegate.currentConfiguration 获得
  RouteInformation restoreRouteInformation(T configuration) => null;
}

RouteInformationProvider

主要负责通知 RouteInformation 变化

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

常用它初始化路由

routeInformationProvider: PlatformRouteInformationProvider(
  initialRouteInformation: const RouteInformation(
    location: '/mainpage',  
  ),
),

RouterDelegate

创建和配置?Navigator?的代理,跟之前写的?Navigator demo?差不多。不同的是增加对?浏览器输入修改URL,?浏览器返回键安卓物理返回键?的处理。

abstract class RouterDelegate<T> extends Listenable {
  
  /// 初始化路由会调用该方法
  Future<void> setInitialRoutePath(T configuration) {

    return setNewRoutePath(configuration);

  }
  
  /// 新增路由比如 浏览器中输入一个新URL,或者在代码设置初始化路由
  Future<void> setNewRoutePath(T configuration);


  /// `浏览器返回键`,`安卓物理返回键` 会调用该方法
  Future<bool> popRoute();
  
  /// RouteInformationParser.restoreRouteInformation 会
  /// 获取该值用于报告给引擎,特别是在 Web 应用中
  T get currentConfiguration => null;
  
  /// 返回 Navigator
  Widget build(BuildContext context);

}

PopNavigatorRouterDelegateMixin

帮你实现了 RouterDelegate 中的?popRoute?方法,不是必须的。

mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
  /// The key used for retrieving the current navigator.
  ///
  /// When using this mixin, be sure to use this key to create the navigator.
  GlobalKey<NavigatorState> get navigatorKey;

  @override
  Future<bool> popRoute() {
    final NavigatorState navigator = navigatorKey?.currentState;
    if (navigator == null)
      return SynchronousFuture<bool>(false);
    return navigator.maybePop();
  }
}

源码分析

前面只是讲了哪些 api 解决了哪些问题,这里我们来追踪一下官方是如何实现的,我在浏览器里面输入一个新的URL。

  • 从图上最后一步,很明显看来这是一个从引擎过来的原生方法

  • 到了_handleNavigationInvocation 方法,pop?和?push?都在这里了,这里就是接收来之引擎的通知包括 浏览器输入,浏览器和安卓物理返回按钮点击。

  Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
    switch (methodCall.method) {
      case 'popRoute':
        return handlePopRoute();
      case 'pushRoute':
        return handlePushRoute(methodCall.arguments as String);
      case 'pushRouteInformation':
        return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
    }
    return Future<dynamic>.value();
  }
  
  • 在方法里面,分别给注册过的?WidgetsBindingObserver?分发事件,当发现处理之后,return 掉。(这里预埋了嵌套?Navigator?的坑)
  Future<void> handlePushRoute(String route) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPushRoute(route))
        return;
    }
  }
  
 Future<void> handlePopRoute() async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPopRoute())
        return;
    }
    SystemNavigator.pop();
  }
  
  Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (
        await observer.didPushRouteInformation(
          RouteInformation(
            location: routeArguments['location'] as String,
            state: routeArguments['state'] as Object,
          )
        )
      )
      return;
    }
  }  
  • WidgetsApp 继承了 WidgetsBindingObserver,在?WidgetsBinding.instance.addObserver(this)?中把自己加入?_observers?里面
class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {

  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
  }
  
    @override
  Future<bool> didPushRoute(String route) async {
  }
  • 而?PlatformRouteInformationProvider?在Router 初始化的时候 addListener的时候把自己加到?_observers中。
class _RouterState<T> extends State<Router<T>> {
  @override
  void initState() {
    super.initState();
widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
  @override
  void addListener(VoidCallback listener) {
    if (!hasListeners)
      WidgetsBinding.instance.addObserver(this);
    super.addListener(listener);
  }

最终我们就能获取到引擎告诉我们的路由变化了!

BackButtonDispatcher

包括?RootBackButtonDispatcher?和?ChildBackButtonDispatcher
主要是为了解决?Navigator 嵌套?,返回按钮优先级的问题。

举个栗子:

1.MyApp?是一个?Navigator, 初始页面为?NestedMainPage,中间一个按钮点击之后?push?到?ChildRouterPage。 现在 第一个?Navigator?中有 2个页面(NestedMainPage,ChildRouterPage)

2.ChildRouterPage?也是一个?Navigator,初始页面为?NestedTestPage,中间一个按钮点击之后?push?到?TestPageA。 现在 第一个?Navigator?中有 2个页面(NestedTestPage,TestPageA)

3.现在我们可以看到的是?TestPageA, 那么现在按?安卓物理返回键?或者?浏览器返回键,是什么现象?

4.页面退回到了?NestedMainPage,这一定不是大家想要的结果吧?

那么我们怎么解决这个问题呢?前面我们知道我们是有办法监听?安卓物理返回键?或者?浏览器返回键?的,也就是?_handleNavigationInvocation?方法中的?popRoute?回调,但是优先处理的第一个能够?pop?的?Navigator(还记得分发的时候那个坑吗)。实际上我们可以自己来决定?popRoute?回调作用于哪个一个?Navigator。我们只需要在第2个 Router 这里做以下操作。

获取到上一个?Router?中?backButtonDispatcher,并且获取优先级。

 Widget build(BuildContext context) {
    final ChildBackButtonDispatcher childBackButtonDispatcher =
        Router.of(context)
            .backButtonDispatcher
            .createChildBackButtonDispatcher();
    childBackButtonDispatcher.takePriority();
    return Router<RouteSettings>(
      backButtonDispatcher: childBackButtonDispatcher,
    );
  }

源码分析:

  • 很熟悉的东西?WidgetsBindingObserveraddCallback?的时候把自己加到监听?_observers?,等待引擎传递事件,WidgetsBinding?中分发。
class RootBackButtonDispatcher extends BackButtonDispatcher with WidgetsBindingObserver {
  RootBackButtonDispatcher();

  @override
  void addCallback(ValueGetter<Future<bool>> callback) {
    if (!hasCallbacks)
      WidgetsBinding.instance.addObserver(this);
    super.addCallback(callback);
  }

  @override
  void removeCallback(ValueGetter<Future<bool>> callback) {
    super.removeCallback(callback);
    if (!hasCallbacks)
      WidgetsBinding.instance.removeObserver(this);
  }

  @override
  Future<bool> didPopRoute() => invokeCallback(Future<bool>.value(false));
}
  • 在调用?takePriority?方法时候讲自己加到?parent?的?children?当中,确保自己是最后一个,并且清空了自己的?children
class ChildBackButtonDispatcher extends BackButtonDispatcher {
  ChildBackButtonDispatcher(this.parent) : assert(parent != null);
  final BackButtonDispatcher parent;
  @protected
  Future<bool> notifiedByParent(Future<bool> defaultValue) {
    return invokeCallback(defaultValue);
  }

  @override
  void takePriority() {
    parent.deferTo(this);
    super.takePriority();
  }
  
  /// BackButtonDispatcher 中的实现,方便讲解
  void deferTo(ChildBackButtonDispatcher child) {
    assert(hasCallbacks);
    _children ??= <ChildBackButtonDispatcher>{} as LinkedHashSet<ChildBackButtonDispatcher>;
    _children.remove(child); // child may or may not be in the set already
    _children.add(child);
  }
  
  /// BackButtonDispatcher 中的实现,方便讲解
  void takePriority() {
    if (_children != null)
      _children.clear();
  }
}
  • 在?invokeCallback?方法,从?children?最后一个开始遍历(这就是为啥在?deferTo?方法中先?remove,后?add),看谁 handle 了?didPopRoute?事件,如果处理了就停止。
  Future<bool> invokeCallback(Future<bool> defaultValue) {
    if (_children != null && _children.isNotEmpty) {
      final List<ChildBackButtonDispatcher> children = _children.toList();
      int childIndex = children.length - 1;

      Future<bool> notifyNextChild(bool result) {
        // If the previous child handles the callback, we returns the result.
        if (result)
          return SynchronousFuture<bool>(result);
        // If the previous child did not handle the callback, we ask the next
        // child to handle the it.
        if (childIndex > 0) {
          childIndex -= 1;
          return children[childIndex]
            .notifiedByParent(defaultValue)
            .then<bool>(notifyNextChild);
        }
        // If none of the child handles the callback, the parent will then handle it.
        return super.invokeCallback(defaultValue);
      }

      return children[childIndex]
        .notifiedByParent(defaultValue)
        .then<bool>(notifyNextChild);
    }
    return super.invokeCallback(defaultValue);
  }
  • 因为在?Router?里面有增加对?BackButtonDispatcher?的监听(?源码中位置),所以最终会通知到RouterDelegate.popRoute

Navigator 2.0 总结

  • 通过对?Navigator.pages?的管理,实现对路由栈的完全掌握。
  • 通过?Router以及相关的?Api?解决浏览器输入修改URL,?浏览器返回键安卓物理返回键?与原生交互的问题,和对?Navigator代理和配置。
  • 通过?BackButtonDispatcher?处理了?Navigator 嵌套?的问题。

看起来,Navigator 2.0 非常完美了,但是实际使用也还是存在一些缺点。

  • 要实现的东西有点多,难道不能?duang?一下就能用吗?
  • Web 浏览器中手动输入参数解析的问题
  • 由于?Route?在?Page?的?createRoute?方法中生成,导致我们没法直接访问到?Route。如果我们要写一个类似?push?有回调参数的方法该怎么办呢?
  Future<T> push<T extends Object>(Route<T> route) {
    _history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
    _flushHistoryUpdates();
    _afterNavigation(route);
    return route.popped;
  }

嗯,是的,法法路由注解 5.0?已经完美支持 Navigator?1.0?和?2.0了,散花!

法法路由注解 5.0

增加引用

添加引用到dependencies,及你需要注解的 project/packages 到pubspec.yaml

dev_dependencies:
  ff_annotation_route_core: any
  ff_annotation_route_library: any

执行?flutter packages get?下载

添加注解

空构造

import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFRoute(
  name: "fluttercandies://mainpage",
  routeName: "MainPage",
)
class MainPage extends StatelessWidget
{
  // ...
}

带参数构造

工具会自动处理带参数的构造,不需要做特殊处理。唯一需要注意的是,你需要使用?argumentImports?为class/enum的参数提供 import 地址。现在你可以使用 @FFArgumentImport()来替代

@FFArgumentImport('hide TestMode2')
import 'package:example1/src/model/test_model.dart';
@FFArgumentImport()
import 'package:example1/src/model/test_model1.dart' hide TestMode3;
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';

@FFRoute(
  name: 'flutterCandies://testPageE',
  routeName: 'testPageE',
  description: 'Show how to push new page with arguments(class)',
  // 为了防止 @FFArgumentImport() 不能完全表达的情况, 依然保留 argumentImports。
  // argumentImports: <String>[
  //   'import \'package:example1/src/model/test_model.dart\';',
  //   'import \'package:example1/src/model/test_model1.dart\';',
  // ],
  exts: <String, dynamic>{
    'group': 'Complex',
    'order': 1,
  },
)
class TestPageE extends StatelessWidget {
  const TestPageE({
    this.testMode = const TestMode(
      id: 2,
      isTest: false,
    ),
    this.testMode1,
  });
  factory TestPageE.deafult() => TestPageE(
        testMode: TestMode.deafult(),
      );

  factory TestPageE.required({@required TestMode testMode}) => TestPageE(
        testMode: testMode,
      );

  final TestMode testMode;
  final TestMode1 testMode1;
}

FFRoute

ParameterDescriptionDefault
name路由的名字 (e.g., "/settings")required
showStatusBar是否显示状态栏true
routeName用于埋点收集数据的页面名字''
pageRouteType路由的类型 (material, cupertino, transparent)-
description路由的描述''
exts其他扩展参数.-
argumentImports某些参数的导入.有一些参数是类或者枚举,需要指定它们的导入地址,现在你可以使用 @FFArgumentImport()来替代-

生成文件

环境

添加 dart 的 bin 的路径到你的系统?$PATH.

cache\dart-sdk\bin

更多信息

不清楚的可以看掘金

激活

pub global activate ff_annotation_route

执行命令

到你的项目根目录下面执行.

ff_route <command> [arguments]

命令参数

可用的命令:

-h, --[no-]help                   帮助信息。

-p, --path                        执行命令的目录,默认当前目录。

-o, --output                      route 和 helper 文件的输出目录路径,路径相对于主项目的 lib 文件夹。

-n, --name                        路由常量类的名称,默认为 `Routes`。

-g, --git                         扫描 git 引用的 package,你需要指定 package 的名字,多个用 `,` 分开
    --routes-file-output          routes 文件的输出目录路径,路径相对于主项目的lib文件夹
    --const-ignore                使用正则表达式忽略一些const(不是全部const都希望生成)
    --[no-]route-constants        是否在根项目中的 `xxx_route.dart` 生成全部路由的静态常量
    --[no-]package                这个是否是一个 package
    --[no-]supper-arguments       是否生成路由参数帮助类

-s, --[no-]save                   是否保存命令到本地。如果保存了,下一次就只需要执行 `ff_route` 就可以了。

注解 Navigator 1.0

完整代码在?example?中

Main.dart

import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/material.dart';
import 'example_route.dart';
import 'example_routes.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage,
      onGenerateRoute: (RouteSettings settings) {
        return onGenerateRoute(
          settings: settings,
          getRouteSettings: getRouteSettings,
          routeSettingsWrapper: (FFRouteSettings ffRouteSettings) {
            if (ffRouteSettings.name == Routes.fluttercandiesMainpage ||
                ffRouteSettings.name ==
                    Routes.fluttercandiesDemogrouppage.name) {
              return ffRouteSettings;
            }
            return ffRouteSettings.copyWith(
                widget: CommonWidget(
              child: ffRouteSettings.widget,
              title: ffRouteSettings.routeName,
            ));
          },
        );
      },
    );
  }
}

Push

Push name

  Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);

Push name with arguments

  • 参数必须是一个?Map<String, dynamic>
  Navigator.pushNamed(
    context,
    Routes.flutterCandiesTestPageE,
    arguments: <String, dynamic>{
      constructorName: 'required',
      'testMode': const TestMode(
        id: 100,
        isTest: true,
      ),
    },
  );
  • 开启 --supper-arguments
  Navigator.pushNamed(
    context,
    Routes.flutterCandiesTestPageE.name,
    arguments: Routes.flutterCandiesTestPageE.requiredC(
      testMode: const TestMode(
        id: 100,
        isTest: true,
      ),
    ),
  );

注解 Navigator 2.0

完整代码在 完整代码在?example1?中

Main.dart

import 'dart:convert';
import 'package:example1/src/model/test_model.dart';
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'example1_route.dart';
import 'example1_routes.dart';

void main() {
  // 工具将处理简单的类型,但是没法处理全部的
  // 比如在浏览器中输入以下地址
  // http://localhost:64916/#flutterCandies://testPageF?list=[4,5,6]&map={"ddd":123}&testMode={"id":2,"isTest":true}
  // queryParameters 将会根据你自身的情况转换成你对应的类型
  FFConvert.convert = <T>(dynamic value) {
    if (value == null) {
      return null;
    }
    print(T);
    final dynamic output = json.decode(value.toString());
    if (<int>[] is T && output is List<dynamic>) {
      return output.map<int>((dynamic e) => asT<int>(e)).toList() as T;
    } else if (<String, String>{} is T && output is Map<dynamic, dynamic>) {
      return output.map<String, String>((dynamic key, dynamic value) =>
          MapEntry<String, String>(key.toString(), value.toString())) as T;
    } else if (const TestMode() is T && output is Map<dynamic, dynamic>) {
      return TestMode.fromJson(output) as T;
    }

    return json.decode(value.toString()) as T;
  };
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final FFRouteInformationParser _ffRouteInformationParser =
      FFRouteInformationParser();

  final FFRouterDelegate _ffRouterDelegate = FFRouterDelegate(
    getRouteSettings: getRouteSettings,
    pageWrapper: <T>(FFPage<T> ffPage) {
      return ffPage.copyWith(
        widget: ffPage.name == Routes.fluttercandiesMainpage ||
                ffPage.name == Routes.fluttercandiesDemogrouppage.name
            ? ffPage.widget
            : CommonWidget(
                child: ffPage.widget,
                routeName: ffPage.routeName,
              ),
      );
    },
  );
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // 初始化第一个页面
      routeInformationProvider: PlatformRouteInformationProvider(
        initialRouteInformation: const RouteInformation(
          location: Routes.fluttercandiesMainpage,
        ),
      ),
      routeInformationParser: _ffRouteInformationParser,
      routerDelegate: _ffRouterDelegate,
    );
  }
}

FFRouteInformationParser

主要用在 Web 平台,当你在浏览器上面输入的时候路由配置转换成为?RouteSettings?,或者当反馈给浏览器的时候将?RouteSettings?转换成路由配置

举个例子:

xxx?a=1&b=2?<=>?RouteSettings(name:'xxx',arguments:<String, dynamic>{'a':'1','b':'2'})

FFRouterDelegate

用于创建和配置导航的委托,它提供?Navigator?中相似的方法.

  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: Routes.flutterCandiesTestPageF.d(
      <int>[1, 2, 3],
      map: <String, String>{'ddd': 'dddd'},
      testMode: const TestMode(id: 1, isTest: true),
    ),
  );

你可以在?test_page_c.dart?页面里面找到更多的例子

Push

Push name

  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageA,
  );

Push name with arguments

  • 参数必须是一个?Map<String, dynamic>
  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: Routes.flutterCandiesTestPageF.d(
      <int>[1, 2, 3],
      map: <String, String>{'ddd': 'dddd'},
      testMode: const TestMode(id: 1, isTest: true),
    ),
  );
  • 开启 --supper-arguments
  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: <String, dynamic>{
        'list': <int>[1, 2, 3],
        'map': <String, String>{'ddd': 'dddd'},
        'testMode': const TestMode(id: 1, isTest: true),
     }
  )

Code Hints

你能这样使用路由 'Routes.flutterCandiesTestPageE', 并且在编辑器中看到代码提示。
包括页面描述,构造,参数类型,参数名字,参数是否必填。

  • 默认
  /// 'This is test page E.'
  ///
  /// [name] : 'flutterCandies://testPageE'
  ///
  /// [routeName] : 'testPageE'
  ///
  /// [description] : 'This is test page E.'
  ///
  /// [constructors] :
  ///
  /// TestPageE : [TestMode testMode, TestMode1 testMode1]
  ///
  /// TestPageE.deafult : []
  ///
  /// TestPageE.required : [TestMode(required) testMode]
  ///
  /// [exts] : {group: Complex, order: 1}
  static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';
  • 开启 --supper-arguments
  /// 'This is test page E.'
  ///
  /// [name] : 'flutterCandies://testPageE'
  ///
  /// [routeName] : 'testPageE'
  ///
  /// [description] : 'This is test page E.'
  ///
  /// [constructors] :
  ///
  /// TestPageE : [TestMode testMode, TestMode1 testMode1]
  ///
  /// TestPageE.test : []
  ///
  /// TestPageE.requiredC : [TestMode(required) testMode]
  ///
  /// [exts] : {group: Complex, order: 1}
  static const _FlutterCandiesTestPageE flutterCandiesTestPageE =
      _FlutterCandiesTestPageE();

  class _FlutterCandiesTestPageE {
    const _FlutterCandiesTestPageE();

    String get name => 'flutterCandies://testPageE';

    Map<String, dynamic> d(
            {TestMode testMode = const TestMode(id: 2, isTest: false),
            TestMode1 testMode1}) =>
        <String, dynamic>{
          'testMode': testMode,
          'testMode1': testMode1,
        };

    Map<String, dynamic> test() => const <String, dynamic>{
          'constructorName': 'test',
        };

    Map<String, dynamic> requiredC({@required TestMode testMode}) =>
        <String, dynamic>{
          'testMode': testMode,
          'constructorName': 'requiredC',
        };

    @override
    String toString() => name;
  }

文章来源:https://blog.csdn.net/jdsjlzx/article/details/124692726
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。