第二代路由最重要的类就是Page,RouterDelegate,Router了,
概述
新版Flutter包含两个版本的导航器,分别是Navigator 1.0和Navigator 2.0。Navigator 2.0是从Flutter 1.22版开始引入的一个全新的导航器,其相比1.0功能更多也更加的灵活,当然了从使用的难易程度上来说Navigator 2.0比Navigator 1.0要难上手一些。
QA:Navigator 2.0出来后Navigator 1.0还能继续用吗?
相信很多同学有这样的疑问,Navigator 2.0是对Navigator 1.0的补充,目前Flutter官方还没有将Navigator 1.0从Flutter中移除的计划,所以大家可以根据需要选择使用这两种导航器。当然了,作为从事Flutter开发的同学如果对Flutter的新特性,比如Navigator 2.0一点都不了解那么肯定是说不过去的,那么接下来我们就来重点学习下Navigator 2.0的特性、原理以及使用技巧。
QA:选择Navigator 2.0还是fluro?
fluro也是一个不错的Flutter导航框架,它和Navigator没有绝对的好与坏,主要区别是一个是社区的,一个是官方的。fluro的出现主要是解决Navigator 1.0功能弱的问题,但这在Navigator 2.0已经得到了很好的缓解。
- 如果你的项目之前是有的就是fluro,那么可以根据需要继续使用;
- 如果你的项目用的是Navigator 1.0,那么可以择机升级到Navigator 2.0;
- 如果你的项目是一个新的项目没有历史包袱,那么可以一步到位直接上手Navigator 2.0;
QA:为什么Navigator 2 比Navigator 1 复杂这么多?
- Navigator 2 为了提供对Flutter Web的支持新引入了很多的API和概念,比如:Pag、Router、RouterDelegate、RouteInformationParser,除此之外还需要自己管理路由栈,比Navigator 1 的复杂度高出了一个量级;
- 但这些API并不是必须要是要实用的,对应一个APP的导航框架来说只需要用到Router、Page、RouterDelegate就可以了,其中Router、Page的使用比较简单,主要是RouterDelegate比较复杂;
- RouterDelegate中最为核心的一点是在build方法中来实现对路由栈的管理,这个参考课程中的设计就可以了;
- 对Navigator 2的使用和理解不仅仅停留在看视频上,要参考着课程中的讲解,动手去写多写几遍,以及跟着后面的实战项目按部就班的学习,然后回过头来在来看之前不理解的部分,就会豁然开朗了;
Navigator 2.0特性
- 支持自定义页面的路由栈;
- 支持一次打开或关闭多个页面;
- 支持删除当前页面下的页面;
那这些特性能都是Navigator 1.0没有或很难实现的一个功能。
Navigator 1.0主要功能回顾
打开和关闭页面是平时开发中最常用的一个功能,那么在Navigator 1.0中最常用的打开和关闭页面的方式是什么呢?
打开一个页面
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
关闭一个页面
Navigator.pop(context);
走进Navigator 2.0
Navigator 2.0 提供了一系列全新的接口,可以实现将路由状态成为应用状态的一部分,并提供解析来自底层平台如:(Web URL)的路由的功能,新增的 API 如下:
- Page:用来表示 Navigator 路由栈中各个页面的不可变对象;
- Page是个抽象类通常使用它的派生类:MaterialPage或CupertinoPage;
- Router:用来配置要由 Navigator 展示的页面列表,通常,该页面列表会根据系统或应用程序的状态改变而改变;
- 除了可以直接使用Router本身外还可以使用MaterialApp.router()来创建Router;
- RouterDelegate:定义应用程序中的路由行为,例如 Router 如何知道应用程序状态的变化以及如何响应;
- 主要的工作就是监听RouteInformationParser和应用状态,并使用当前列表来构建Pages;
- RouteInformationParser:可缺省,主要应用与web,持有RouteInformationProvider 提供的 RouteInformation ,可以将其解析为我们定义的数据类型;
- BackButtonDispatcher:响应后退按钮,并通知 Router
Tips:上面API中BackButtonDispatcher是用到的情况很少,另外对应移动端APP开发来说我们只需要用到Navigator 2.0 中的Page、Router、RouterDelegate这三个API即可,RouteInformationParser与RouteInformationProvider主要是应用于开发web网站的路由用的;
下图展示了 RouterDelegate 与 Router、RouteInformationParser 在一起的交互和应用程序的状态:
流程解析:
- 当系统打开新页面(如 “/detail”)时,RouteInformationParser 会将其转换为应用中的具体数据类型 T(如:BiliRoutePath);
- 该数据类型会被传递给 RouterDelegate 的 setNewRoutePath 方法,我们可以在这里更新路由状态;
- notifyListeners 会通知 Router 重建 RouterDelegate(通过 build() 方法);
- RouterDelegate.build() 返回一个新的 Navigator 实例,并最终展示出我们想要打开的页面;
下面写了一个小栗子,仅供参考:首先定义RouterDelegate的子类:这里需要注意的是重写ChangeNotify方法的时候,不需要重写addListener和removeListener,否则得到的后果就是有可能跳转不了,我测试过的,在这个地方踩了坑,不希望后来人在同样的位置翻车。
一:自定义RouterDelegate 包含页面堆栈信息
import 'package:bilibili_demo/nv2/detail.dart';
import 'package:bilibili_demo/nv2/navigator2.dart';
import 'package:flutter/material.dart';
MaterialPage wrapPage(
{required Widget child, Object? args, required String name}) {
return MaterialPage(
child: child, name: name, arguments: args, key: ValueKey(child.hashCode));
}
class MyRouterDelegate extends RouterDelegate<String>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<String> {
bool isDetail = false;
List<MaterialPage> pages = [];
/// 管理的页面堆栈
final _stack = <String>["/"];
List<String> get stack => List.unmodifiable(_stack);
static MyRouterDelegate of(BuildContext context) {
final delegate = Router.of(context).routerDelegate;
assert(delegate is MyRouterDelegate, "delegate mush match");
return delegate as MyRouterDelegate;
}
void push(String newRouter, {Object? args}) {
if (_stack.contains(newRouter)) return;
_stack.add(newRouter);
addPageMap(newRouter, args: args);
notifyListeners();
}
/// 向pageMap中加入路由信息
void addPageMap(String router, {Object? args}) {
MaterialPage page = wrapPage(child: Container(), name: router);
switch (router) {
case "/detail":
page = wrapPage(child: DetailDemo(), name: router, args: args);
break;
case "/":
page = wrapPage(child: Navigator2Home(), name: router, args: args);
break;
}
_pageMap.addAll({router: page});
}
void removePageMap(String router) {
_pageMap.remove(router);
}
final Map<String, Page> _pageMap = <String, Page>{
"/": wrapPage(child: Navigator2Home(), name: "/"),
};
void remove(String routername) {
if (!_stack.contains(routername)) return;
if (routername == "/") return;
_stack.remove(routername);
removePageMap(routername);
notifyListeners();
}
// final RouteFactory onGenerateRouter;
@override
// TODO: implement currentConfiguration
String? get currentConfiguration {
if (_stack.isNotEmpty) return _stack.last;
return null;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Navigator(
key: navigatorKey,
pages: [for (String name in _stack) _pageMap[name]!],
onPopPage: (router, value) {
if (router.didPop(value)) {
print("value is $value router is ${router.settings.name}");
remove(router.settings.name!);
return true;
}
print("false");
return false;
},
);
}
@override
Future<bool> popRoute() async {
// TODO: implement popRoute
print("popRouter");
// return true;
return super.popRoute();
}
@override
Future<void> setInitialRoutePath(String configuration) {
print("init config is $configuration");
// TODO: implement setInitialRoutePath
return super.setInitialRoutePath(configuration);
}
@override
Future<void> setNewRoutePath(configuration) async {
// TODO: implement setNewRoutePath
print("new router is $configuration");
}
@override
// TODO: implement navigatorKey
GlobalKey<NavigatorState>? get navigatorKey => GlobalKey<NavigatorState>();
}
class MyRouterInformationParser extends RouteInformationParser<String> {
@override
Future<String> parseRouteInformation(
RouteInformation routeInformation) async {
// TODO: implement parseRouteInformation
print("information is $routeInformation");
return routeInformation.location!;
}
@override
RouteInformation? restoreRouteInformation(configuration) {
print("config is $configuration");
// TODO: implement restoreRouteInformation
RouteInformation? info = super.restoreRouteInformation(configuration);
print("info is $info");
return info;
}
}
二:main.dart里面使用RouterDelegate:
void main() async {
try {
await DbManager.init();
print("db init over");
} catch (e) {
printLog("==> e is $e");
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final RouterDelegate<String> _delegate = MyRouterDelegate();
/// 想使用parse的可以自行添加
final RouteInformationParser<String> _parser = MyRouterInformationParser();
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "flutter navigator2",
home: Router(routerDelegate: _delegate),
);
}
}
定义两个页面一个是homePage,一个是detail,
三:路由使用
MyRouterDelegate.of(context).push("/detail");
import 'package:bilibili_demo/nv2/my_router_delegate.dart';
import 'package:flutter/material.dart';
class Navigator2Home extends StatefulWidget {
const Navigator2Home({
Key? key,
}) : super(key: key);
@override
State<Navigator2Home> createState() => _Navigator2HomeState();
}
class _Navigator2HomeState extends State<Navigator2Home> {
void goDetail() {
MyRouterDelegate.of(context).push("/detail");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: goDetail, child: Text("go detail"))
],
),
),
);
}
}
detail页面的逻辑比较简单我就不贴了,至此navigator2的内容完毕,是不是很简单