Navigator2 为了提供 Flutter Web的支持新引入了很多的API 概念,比如: Page, Router, RouteDelegate, RouteInformationParser, 除此之外还需要自己管理路由栈,比如 Navigator 1 的复杂度高出了很多。但是这些 API 并不都是必须的,对于一个App的导航框架来说,只需要 Router, Page, RouteDelegate 就可以了,其中 Router, Page 的使用比较简单,主要是 RouterDelegate 比较复杂。
RouteDelegate 中最为核心的一点就是在build方法中来实现对路由栈的管理。
Navigator2 特性:
1. 支持自定义页面的路由栈
2. 支持一次打开或者关闭多个页面
3. 支持删除当前页面下的页面
以上特性都是Navigator 1.0 没有或者很难实现的功能。
Navigator 2.0 提供了一系列全新的接口, 可以实现将路由状态成为应用状态的一部分,并提供解析来自底层平台如 WebURL 的路由的功能,新增的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 在一起的交互和应用程序的状态:
流程解析:
1. 当系统打开新页面(如 “/home”)时,RouteInformationParser 会将其转换为应用中的具体数据类型 T(如:BiliRoutePath);
2. 该数据类型会被传递给 RouterDelegate 的 setNewRoutePath 方法,我们可以在这里更新路由状态;
3. notifyListeners 会通知 Router 重建 RouterDelegate(通过 build() 方法)RouterDelegate.build() 返回一个新的 Navigator 实例,并最终展示出我们想要打开的页面;
具体使用Navigator2的步骤:
1. 创建XXPath类;
2. 构建使用MaterialPage 构建Page;
3. 创建XXRouteDelegte类,继承 RouterDelegate,并with 混入ChangeNotifier, PopNavigatorRouterDelegateMixin, 在build方法中返回Navigator;
4. 创建Router对象和XXRouteDelegate对象,并将XXRouteDelegate对象传入Router的构造方法中,最后将Router对象传入MaterialApp的home中。
5. 在XXRouteDelegate的build方法中利用pages来进行路由栈的管理。
一、创建XXPath类
//main.dart
class MxRoutePath {
final location;
const MxRoutePath.home() : location = "/";
const MxRoutePath.detail() : location = "/detail";
}
二、创建各个不同的页面,Login, Registration, Home, VideoList, VideoDetail, 并将这些页面包装成Page,代码如下:
//hi_navigator.dart
pageWrap(Widget child,{VideoModel model}) {
return MaterialPage(key: ValueKey(child.hashCode), child: child); //ValueKey是用来标记唯一的
//return MaterialPage(key:ValueKey(model??child.hashCode), child: child);
}
三、创建XXRouteDelegate
//main.dart
class MXRouteDelegate extends RouterDelegate<MxRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<MxRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
MxRoutePath path;
List<MaterialPage> pages = [];
RouteStatus _routeStatus = RouteStatus.homePage;
var videoType;
//为 Navigator 设置一个key, 必要的时候可以通过 navigatorKey.currentState 获取 NavigatorState 对象
MXRouteDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
HiNavigator.instance().registerRouteJumpListener(RouteJumpListener(onJumpTo: (RouteStatus status, {Map args}) {
_routeStatus = status;
print("_routeStatus ${_routeStatus.name}");
if (status == RouteStatus.homePage) {
//获取传递过来的参数
} else if (status == RouteStatus.videoListPage) {
//获取传递过来的视频类别
videoType = args['videoType'];
} else if (status == RouteStatus.videoDetailPage) {
//获取传递过来的视频详情
videoModel = args['videoModel'];
print('videoModel ${videoModel.vid}');
}
notifyListeners(); // 和setState 一样,会重新调用build 方法
}));
}
RouteStatus get routeStatus {
if (!hasLogin && _routeStatus != RouteStatus.registrationPage) {
//没有登录,需要跳转到登录页面
return _routeStatus = RouteStatus.loginPage; //跳转到登录页面
} else if (videoType != null && _routeStatus == RouteStatus.videoListPage) {
return _routeStatus = RouteStatus.videoListPage;
} else if (videoModel != null && _routeStatus == RouteStatus.videoDetailPage) {
return _routeStatus = RouteStatus.videoDetailPage;
}
return _routeStatus;
}
bool get hasLogin => true; //判断是否已经登录
VideoModel videoModel;
@override
Widget build(BuildContext context) {
int index = getRouteIndex(pages, routeStatus);
List<MaterialPage> tmpPages = pages;
if (index != -1) {
tmpPages = tmpPages.sublist(0, index);
}
var page;
if (routeStatus == RouteStatus.homePage) {
pages.clear();
page = pageWrap(HomePage());
} else if (routeStatus == RouteStatus.videoListPage) {
page = pageWrap(VideoListPage(videoType));
} else if (routeStatus == RouteStatus.videoDetailPage) {
page = pageWrap(VideoDetailPage(videoModel), model: videoModel);
} else if (routeStatus == RouteStatus.loginPage) {
pages.clear();
page = pageWrap(LoginPage());
} else if (routeStatus == RouteStatus.registrationPage) {
page = pageWrap(RegistrationPage());
}
tmpPages = [...tmpPages, page]; //...展开运算符
//通知页面发生了变化
HiNavigator.instance().notify(tmpPages, pages);
//将最新的页面堆栈赋值给pages
pages = tmpPages;
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: (route, result) {
//注意:当栈内只有一个页面的时候是不会执行onPopPage回调
//登录页没有登录返回拦截
print("是否进行回退...");
if (route.settings is MaterialPage) {
if ((route.settings as MaterialPage).child is LoginPage) {
print("是否在登录页进行回退...");
if (!hasLogin) {
return false; //在退出登录页面的时候,加以拦截,不能直接退出
}
}
if ((route.settings as MaterialPage).child is RegistrationPage) {
print("是否在注册页进行回退...");
}
}
//在这里可以控制是否可以返回
if (!route.didPop(result)) {
return false;
}
var tmpPages = [...pages];
pages.removeLast(); //移除栈顶到页面
HiNavigator.instance().notify(pages, tmpPages);
//return true 表示在退出的时候,不阻拦
return true;
},
);
}
@override
Future<void> setNewRoutePath(MxRoutePath path) async {
this.path = path;
print("setNewRoutePath ${path.location}");
}
}
此处重点关注build 方法是如何返回Navigator对象的,其他的暂时不用考虑。比如使用 GlobalKey<NavigatorState>来创建 key,并赋值给Navigator的key属性,利用List<MaterialPage> 来创建 pages 对象来赋值给Navigato的pages属性,onPopPage 的返回值是用来判断是否将当前即将退出的Page给拦截退出,true 表示不拦截退出,false 表示拦截退出。
四、创建Router对象和XXRouteDelegate对象
//main.dart
class MyApp extends StatelessWidget {
MXRouteDelegate routeDelegate = MXRouteDelegate();
@override
Widget build(BuildContext context) {
//MaterialApp.router(routeInformationParser: routeInformationParser, routerDelegate: routerDelegate)
Widget widget = Router(routerDelegate: routeDelegate);
return MaterialApp(
home: widget,
);
}
}
五、XXRouteDelegate的build方法内部进行页面栈的管理
整体的思路就是pages中由MaterialPage构成的栈是最终整个应用的路由栈,pages列表最后的page就是显示在最前面给用户看到的。具体的逻辑就是判断当前的枚举类型 routeStatus 来判断当前路由栈中是否存在与该 routeStatus 相匹配的 page, 如果存在,则返回该page在路由栈pages的下标,然后在列表pages中移除所有的该下标以后的所有page,包括当前该index对应的page【其实也可以不用删除,看需求】,如果不存在与status对应的page, 那么就创建对应的page,然后添加到pages中。在onPopPage中,如果确定要退出当前页,则在返回true 不进行拦截的前面删除对应的栈顶页面。其他的代码是关于页面跳转和页面切换监听的,代码都在下面,不解释了。
//hi_navigator.dart
//创建Page
import 'package:flutter/material.dart';
import 'package:flutter_navigator/home_page.dart';
import 'package:flutter_navigator/login_page.dart';
import 'package:flutter_navigator/registration_page.dart';
import 'package:flutter_navigator/video_detail_page.dart';
import 'package:flutter_navigator/video_list_page.dart';
import 'package:flutter_navigator/video_mo.dart';
pageWrap(Widget child,{VideoModel model}) {
return MaterialPage(key: ValueKey(child.hashCode), child: child); //ValueKey是用来标记唯一的
//return MaterialPage(key:ValueKey(model??child.hashCode), child: child);
}
//1.主页 homePage 2. 列表页 videoListPage 3.详情页 VideoDetailPage
// 1-->2-->3-->1
//1.先定义RouteStatus
//2.根据当前的RouteStatus得到当前页面在路由栈中的位置 index
//3.根据index,排除包括index所指页面的以上所有页面 从List<MaterialPage>
//4.根据RouteStatus 生成新的页面, 然后放入到列表中
//5.更新路由栈页面数据
enum RouteStatus {homePage, videoListPage, videoDetailPage, loginPage, registrationPage, unknownPage}
//给枚举扩展属性
extension RouteStatusExtension on RouteStatus{
String get name =>
['homePage', 'videoListPage', 'videoDetailPage','loginPage','registrationPage','unknownPage'][index];
}
//获取路由在路由栈中的index
int getRouteIndex(List<MaterialPage> pages, RouteStatus status){
//判断如果栈顶的是VideoDetailPage 那么就不去销毁旧的
if(pages.isNotEmpty && pages.last.child is VideoDetailPage){
return -1;
}
for(int index= 0; index< pages.length; index++){
MaterialPage page = pages[index];
if (getPageStatus(page) == status){
return index;
}
}
return -1;
}
RouteStatus getPageStatus(MaterialPage page){
if (page.child is HomePage){
return RouteStatus.homePage;
} else if (page.child is VideoListPage){
return RouteStatus.videoListPage;
} else if (page.child is VideoDetailPage){
return RouteStatus.videoDetailPage;
} else if (page.child is LoginPage){
return RouteStatus.loginPage;
} else if (page.child is RegistrationPage){
return RouteStatus.registrationPage;
} else {
return RouteStatus.unknownPage;
}
}
//定义页面切换生命周期逻辑 比如从哪一个页面切换到一个页面
typedef RouteChangeListener(RouteStatusInfo current, RouteStatusInfo pre);
//路由信息
class RouteStatusInfo {
final RouteStatus routeStatus;
final Widget page;
RouteStatusInfo(this.routeStatus, this.page);
}
//1.定义跳转回调
typedef OnJumpTo = void Function(RouteStatus routeStatus, {Map args});
//2.定义一个接口,准备在跳转的时候进行callback
abstract class _RouteJumpListener {
void onJumpTo(RouteStatus routeStatus, {Map args});
}
//3.定义Listener, 跳转的监听类, 用于注册在HiNavigator
class RouteJumpListener {
final OnJumpTo onJumpTo;
RouteJumpListener({this.onJumpTo});
}
//4.定义 HiNavigator单例,用于注册和监听跳转
class HiNavigator extends _RouteJumpListener {
static HiNavigator _instance;
RouteJumpListener _routeJumpListener;
HiNavigator._();
static HiNavigator instance() {
if (_instance == null) {
_instance = HiNavigator._();
}
return _instance;
}
//从别处主动调用该方法,传入RouteStatus 和 必要的参数
@override
void onJumpTo(RouteStatus routeStatus, {Map args}) {
_routeJumpListener.onJumpTo(routeStatus, args:args);
}
void registerRouteJumpListener(RouteJumpListener listener){
this._routeJumpListener = listener;
}
//添加页面监听
List<RouteChangeListener> _listeners = [];
void addRouteChangeListener(RouteChangeListener listener){
if (!_listeners.contains(listener)){
_listeners.add(listener);
}
}
//移除页面监听
void removeRouteChangeListener(RouteChangeListener listener){
_listeners.remove(listener);
}
//触发生命周期callback
void notify(List<MaterialPage> currentPages, List<MaterialPage> prePages){
if (currentPages == prePages){
return;
}
var current = RouteStatusInfo(getPageStatus(currentPages.last), currentPages.last.child);
_notify(current);
}
RouteStatusInfo _current; //当前页面的信息
void _notify(RouteStatusInfo current) {
print("notify current page --> ${current.routeStatus.name} prePage--->${_current?.routeStatus?.name}");
_listeners.forEach((listener) {
listener(current, _current);
});
_current = current;
}
}
//video_mo.dart
class VideoModel {
int vid;
VideoModel(this.vid);
}
其他页面代码:
//login_page.dart
import 'package:flutter/material.dart';
import 'hi_navigator.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
titleSpacing: 0,
leading: BackButton(),
title: Text('loginPage'),),
body: Container(
child: Column(
children: [
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('点击跳转到注册页面'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.registrationPage);
}),
),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('登录成功点击跳转到首页'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.homePage);
}),
),
],
),
),
);
}
}
//registration_page.dart
import 'dart:async';
import 'package:flutter/material.dart';
class RegistrationPage extends StatefulWidget {
const RegistrationPage({Key key}) : super(key: key);
@override
_RegistrationPageState createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State<RegistrationPage> {
bool needNotice = true; //表示提醒填写个人信息
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('RegistrationPage'),
titleSpacing: 0,
leading: BackButton(),
),
body: Container(
child: Column(
children: [
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: MediaQuery.of(context).size.width,
child: Text('点击跳转到登录页面'),
onPressed: () {
//HiNavigator.instance().onJumpTo(RouteStatus.loginPage);
Navigator.of(context).pop();
}),
),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: MediaQuery.of(context).size.width,
child: Text('点击更新填写用户信息'),
onPressed: () {
needNotice = false;
setState(() {
});
}),
),
],
),
),
),
onWillPop: _requestPop
);
}
Future<bool> _requestPop() async{
bool result = await _showNoticeDialog();
print("_requestPop $result");
return Future.value(result);
}
Future<bool> _showNoticeDialog() async{
bool result = await showDialog(
context: context,
barrierDismissible: false, //是否触摸对话框之外能够消失对话框
builder: (BuildContext context) {
return AlertDialog(
title: Text('请填写相关的个人信息'),
content: SingleChildScrollView(
child: ListBody(
children: [
Text('这是标题'),
Text('这是内容'),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('退出')),
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('取消'))
],
);
});
return Future.value(result);
}
}
//home_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_navigator/hi_navigator.dart';
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver{
var routeChangeListener;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
HiNavigator.instance().addRouteChangeListener(this.routeChangeListener=
(RouteStatusInfo current, RouteStatusInfo pre){
print("onChange current:${current.routeStatus.name} pre:${pre?.routeStatus?.name}");
if (widget == current.page || current.page is HomePage){
print("HomePage onResume...");
} else if (widget == pre?.page || pre?.page is HomePage){
print("HomePage onPause");
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage'),),
body: Container(
child: Column (
children: [
FractionallySizedBox(
widthFactor: 1,
child: Container(
alignment: Alignment.center,
child: Text('首页'),
height: 45,
),
),
Padding(
padding: EdgeInsets.only(left: 10, right: 10),
child: FractionallySizedBox(
widthFactor: 1,
child: MaterialButton (
color: Colors.blue,
padding: EdgeInsets.all(10),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.videoListPage, args: {"videoType": "scenery"});
},
child:Text('跳转到视频列表页面', style: TextStyle(color: Colors.white),)
),
))
],
),
),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
HiNavigator.instance().removeRouteChangeListener(routeChangeListener);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
//print("didChangeAppLifecycleState ${state}");
switch(state){
case AppLifecycleState.resumed://可以展示UI
print("didChangeAppLifecycleState onResume");
break;
case AppLifecycleState.inactive://不可交互
print("didChangeAppLifecycleState inactive");
break;
case AppLifecycleState.paused://进入后台
print("didChangeAppLifecycleState paused");
break;
case AppLifecycleState.detached://销毁的时候
print("didChangeAppLifecycleState detached");
break;
}
}
//监听系统的明亮度发生变化
@override
void didChangePlatformBrightness() {
super.didChangePlatformBrightness();
print("didChangePlatformBrightness");
}
}
//video_list_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_navigator/hi_navigator.dart';
import 'package:flutter_navigator/video_mo.dart';
class VideoListPage extends StatefulWidget {
final String videoType;
const VideoListPage(this.videoType, {Key key}) : super(key: key);
@override
_VideoListPageState createState() => _VideoListPageState();
}
class _VideoListPageState extends State<VideoListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('VideoListPage'),),
body: Container(
child: Column(
children: [
Text('视频列表页面${widget.videoType}'),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('点击跳转到视频详情页面'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.videoDetailPage, args: {'videoModel':VideoModel(100)});
}),
)
],
),
),
);
}
}
//video_detail_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_navigator/video_mo.dart';
import 'hi_navigator.dart';
class VideoDetailPage extends StatefulWidget {
final VideoModel videoModel;
const VideoDetailPage(this.videoModel,{Key key}) : super(key: key);
@override
_VideoDetailPageState createState() => _VideoDetailPageState();
}
class _VideoDetailPageState extends State<VideoDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('VideoDetailPage'),),
body: Container(
child: Column (
children: [
Text('视频详情页面 ${widget.videoModel?.vid}'),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('点击跳转到首页'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.homePage);
}),
),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('点击跳转到登录页'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.loginPage);
}),
),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: MaterialButton(
minWidth: double.infinity,
child: Text('点击继续跳转详情页'),
onPressed: (){
HiNavigator.instance().onJumpTo(RouteStatus.videoDetailPage, args: {'videoModel':VideoModel(200)});
}),
)
],
),
),
);
}
}