最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Flutter 路由我定

    正文概述 掘金(法的空间)   2021-02-23   1058

    相关阅读

    基于 Navigator1.0 :

    • Flutter 法法路由注解
    • Flutter 法法注解路由 2.0
    • Flutter 法法注解路由 4.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/AmarkForAdd()N/AmarkForPop([dynamic result])markForComplete([dynamic result])markForRemove()

    TransitionDelegate / DefaultTransitionDelegate

    RouteTransitionRecordTransitionDelegate.resolve 中进行设置.

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

    Flutter 路由我定

    DefaultTransitionDelegate 中的大概逻辑如下

    新Pages旧Pages状态
    A=>BA=>B=>CC markForPopA=>CA=>B=>CB markForCompleteA=>B=>C=>DA=>B=>CD markForPushA=>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 的,不懂的可以去回顾下。

    • Flutter 法法路由注解
    • Flutter 法法注解路由 2.0
    • Flutter 法法注解路由 4.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();    
    
    • 完整 demo 地址

    现在看起来,我们能够完美控制整个路由栈了,是不是就够了呢? 答案肯定是不够的,我们还没有处理 浏览器输入修改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。

    Flutter 路由我定

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

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

      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

    包括 RootBackButtonDispatcherChildBackButtonDispatcher。 主要是为了解决 Navigator 嵌套 ,返回按钮优先级的问题。 Flutter 路由我定

    举个栗子:

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

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

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

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

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

    获取到上一个 RouterbackButtonDispatcher,并且获取优先级。

     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 方法时候讲自己加到 parentchildren 当中,确保自己是最后一个,并且清空了自己的 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

    • 完整 demo 地址

    Navigator 2.0 总结

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

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

    • 要实现的东西有点多,难道不能 duang 一下就能用吗?
    • Web 浏览器中手动输入参数解析的问题
    • 由于 RoutePagecreateRoute 方法中生成,导致我们没法直接访问到 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.02.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 地址。

    import 'package:ff_annotation_route/ff_annotation_route.dart';
    
    @FFRoute(
      name: 'flutterCandies://testPageE',
      routeName: 'testPageE',
      description: 'This is test page E.',
      argumentImports: <String>[
        'import \'package:example/src/model/test_model.dart\';',
        'import \'package:example/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")requiredshowStatusBar是否显示状态栏truerouteName用于埋点收集数据的页面名字''pageRouteType路由的类型 (material, cupertino, transparent)-description路由的描述''exts其他扩展参数.-argumentImports某些参数的导入.有一些参数是类或者枚举,需要指定它们的导入地址-

    生成文件

    环境

    添加 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;
      }
    
    

    结语

    • 路由我定,人生中也有很多的十字路,以及选择需要我们去定。选择大于努力,有时候以为只有一条路,其实也可以有另外的选择。有时候以为还有其他的选择,其实只剩下一条路。站在人生十字路口希望我们都不再彷徨,不管是现在的我,还是将来的你,都不希望会后知后觉。
    • Flutter 也两年了,从开始孤自一人研究到现在有很多小伙伴可以讨论, Flutter 让我学习到了很多东西,也认识了更多的人。不知道程序员能写多久的代码,但从重庆轻轨做通信项目到转行来写代码,是真的因为喜欢。我想一个人能做自己喜欢的事情,应该也是一种幸福吧。
    • 很感谢你能读到这里,希望本文对你有所帮助。爱Flutter,爱糖果,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果Flutter 路由我定QQ群:181398081。最最后放上Flutter Candies全家桶,真香。

    Flutter 路由我定


    起源地下载网 » Flutter 路由我定

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元