Flutter 路由我定
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 路由我定相关的知识,希望对你有一定的参考价值。
相关阅读
基于 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=>B | A=>B=>C | C markForPop |
A=>C | A=>B=>C | B markForComplete |
A=>B=>C=>D | A=>B=>C | D markForPush |
A=>B=>D=>C | A=>B=>C | D 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 栗子
- 我们准备一下
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,
);
- 准备
pages
,这就是我们的路由栈,初始化的一个 MainPage。要注意的是,key 必须是一个 唯一的 key。
final List<MyPage> _pages = <MyPage>[
MyPage(
name: 'MainPage', widget: const TestPage('MainPage'), key: UniqueKey()),
];
- 使用 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;
,
),
- 现在你就可以任意去操作路由栈了,下面举几个例子。
新增一个页面,相当于 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,
)
存在下面2种场景:
- RouteInformationProvider => Router , 这种情况发生在有新的路由可用,比如在浏览器中输入一个新URL,或者在代码设置初始化路由。
- Router => RouteInformationProvider, 这种情况只发生在通知引擎改变浏览器 URL。
class RouteInformation
const RouteInformation(this.location, this.state);
/// 比如: `/`, `/path`, `/path/to/the/app`.
final String location;
/// 当前页面的状态,比如滚动位置,必须是可以序列化的对象.
final Object state;
主要负责解析 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;
主要负责通知 RouteInformation 变化
abstract class RouteInformationProvider extends ValueListenable<RouteInformation>
void routerReportsNewRouteInformation(RouteInformation routeInformation)
常用它初始化路由
routeInformationProvider: PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: '/mainpage',
),
),
创建和配置 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,
);
源码分析:
- 很熟悉的东西
WidgetsBindingObserver
,addCallback
的时候把自己加到监听_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
Parameter | Description | Default |
---|---|---|
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;
以上是关于Flutter 路由我定的主要内容,如果未能解决你的问题,请参考以下文章