1、Flutter Router路由封装
路由插件: fluro
1.1 在 pubspec.yaml
中加入下面代码,并获取该依赖包
fluro: "^1.6.3"
1.2 在 lib
文件夹下新建一个 router
文件夹,并新建 router.dart
和 router_handler.dart
两个文件
1.2.1 router_handler.dart
文件
import 'package:fluro/fluro.dart'; // 引入路由包依赖文件
import 'package:flutter/cupertino.dart';
import 'package:new_flutter/pages/page_one.dart'; // page_one为第一个页面
import 'package:new_flutter/pages/page_two.dart'; // page_two为第一个页面
// 创建每个路由的 Handler函数
var pageOne = new Handler(
handlerFunc: (BuildContext context, Map<String,List<String>>params ){
return PageOne(); // PageOne 为 page_one 页面的 StatelessWidget
}
);
var pageTwo = new Handler(
handlerFunc: (BuildContext context, Map<String,List<String>>params ){
return PageTwo(); // PageTwo 为 page_two 页面的 StatelessWidget
}
);
1.2.2 router.dart
文件
import 'package:flutter/cupertino.dart';
import 'package:fluro/fluro.dart'; // 引入路由包依赖文件
import 'package:new_flutter/router/router_handler.dart'; // 每个页面路由对应的 Handler 函数文件
// 定义路由配置类
class Routers {
static String root = '/';
//路由配置
static void configRouters(Router router){
//找不到路由
router.notFoundHandler = new Handler(
handlerFunc: (BuildContext context,Map<String,List<String>> params){
print('ERROR====>ROUTE WAS NOT FONUND!!!');
return;
}
);
//整体配置
router.define('/pageOne', handler: pageOne); // page_one 页面路由
router.define('/pageTwo', handler: pageTwo); // page_two 页面路由
}
}
1.3 在 main.dart
中引入路由信息,并初始化
import 'package:fluro/fluro.dart'; // 引入路由包依赖文件
import 'package:flutter/material.dart';
import 'package:new_flutter/pages/page_one.dart'; // 这里 page_one 作为应用打开页面
import 'package:new_flutter/router/router.dart'; // 路由信息文件
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
@override
Widget build(BuildContext context) {
//路由初始化
final router = Router();
Routers.configRouters(router);
return new MaterialApp(
title: 'Flutter App', // 设备用于为用户识别应用程序的单行描述
theme: ThemeData( // 应用程序小部件使用的颜色。
primaryColor: Color.fromRGBO(222, 225, 231, 1)
),
color: Colors.red, // 在操作系统界面中应用程序使用的主色。
home: PageOne(), // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
navigatorKey: navigatorKey, // 在构建导航器时使用的键
onGenerateRoute: router.generator, // 应用程序导航到指定路由时使用的路由生成器回调
);
}
}
1.4 创建页面 page_one 和 page_two
1.4.1 page_one.dart (这里我做了一个按钮,点击跳转至 page_two 页面)
import 'package:flutter/material.dart';
class PageOne extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: new AppBar( // 头部导航栏
title: new Text('我的 Flutter'),
),
body: new Center(
child: Column(
children:[
OutlineButton(
child: Text("normal"),
onPressed: () {
// pageOne 跳转到 pageTwo页面 ,
Navigator.of(context).pushNamed(
'/pageTwo', // router.dart 中定义的路由访问链接
arguments: { 'id': 1} // arguments 传递的参数
);
},
)
]
),
),
),
);
}
}
1.4.2 page_two.dart (这里我接收从 page_one 页面传递过来的参数)
import 'package:flutter/material.dart';
class PageTwo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取路由传参
print( ModalRoute.of(context).settings.arguments );
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('page Two'),
),
body: new Center(
child: Text('page Two'),
),
),
);
}
}
注意:
MaterialApp({
Key key,
this.title = '', // 设备用于为用户识别应用程序的单行描述
this.home, // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
this.color, // 在操作系统界面中应用程序使用的主色。
this.theme, // 应用程序小部件使用的颜色。
this.routes = const <String, WidgetBuilder>{}, // 应用程序的顶级路由表
this.navigatorKey, // 在构建导航器时使用的键。
this.initialRoute, // 如果构建了导航器,则显示的第一个路由的名称
this.onGenerateRoute, // 应用程序导航到指定路由时使用的路由生成器回调
this.onUnknownRoute, // 当 onGenerateRoute 无法生成路由(initialRoute除外)时调用
this.navigatorObservers = const <NavigatorObserver>[], // 为该应用程序创建的导航器的观察者列表
this.builder, // 用于在导航器上面插入小部件,但在由WidgetsApp小部件创建的其他小部件下面插入小部件,或用于完全替换导航器
this.onGenerateTitle, // 如果非空,则调用此回调函数来生成应用程序的标题字符串,否则使用标题。
this.locale, // 此应用程序本地化小部件的初始区域设置基于此值。
this.localizationsDelegates, // 这个应用程序本地化小部件的委托。
this.localeListResolutionCallback, // 这个回调负责在应用程序启动时以及用户更改设备的区域设置时选择应用程序的区域设置。
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')], // 此应用程序已本地化的地区列表
this.debugShowMaterialGrid = false, // 打开绘制基线网格材质应用程序的网格纸覆盖
this.showPerformanceOverlay = false, // 打开性能叠加
this.checkerboardRasterCacheImages = false, // 打开栅格缓存图像的棋盘格
this.checkerboardOffscreenLayers = false, // 打开渲染到屏幕外位图的图层的棋盘格
this.showSemanticsDebugger = false, // 打开显示框架报告的可访问性信息的覆盖
this.debugShowCheckedModeBanner = true, // 在选中模式下打开一个小的“DEBUG”横幅,表示应用程序处于选中模式
})
1.5 自定义动画路由
很多时候,我们的项目中会有一些特殊的页面切换动效,此时我们就需要自定义路由动画,flutter
中 PageRouteBuilder
提供了一个 Animation
对象,Animation
能够通过结合 Tween
以及 Curve
对象来自定义路由转换动画
PageRouteBuilder
属性
PageRouteBuilder({
RouteSettings settings, // 路由基本信息
@required this.pageBuilder, // 创建跳转的路由页面
this.transitionsBuilder = _defaultTransitionsBuilder, // 自定义的转场效果
this.transitionDuration = const Duration(milliseconds: 300), // 转场动画的持续时间
this.opaque = true, // 是否遮挡整个屏幕
this.barrierDismissible = false,
this.barrierColor, // 路由回退时页面动画阴影颜色
this.barrierLabel,
this.maintainState = true, // 默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设maintainState为false
bool fullscreenDialog = false, // 表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。
})
1.5.1 搭建一个 PageRouteBuilder
/*
* Widget nextPage 下一个路由组件
* Tween tween 定义的页面动画过程
*/
PageRouteBuilder(
// 设置路由基本信息Settings,可通过它获取当前路由、上个页面路由
settings: new RouteSettings(
name: '$nextPage'
),
// 用来生成要显示的Widget
pageBuilder: (context,animation,secondaryAnimation) => nextPage,
// 设置转场动画的效果
transitionsBuilder: (context, animation, secondaryAnimation, child){
return SlideTransition(
position: animation.drive(
tween.chain(CurveTween( curve: Curves.ease))
),
child: child
);
},
// 设置了动画时长
transitionDuration:const Duration(milliseconds: 600)
);
1.5.2 创建 Tween
为了使新页面从底部动画出来,它应该从 Offset(0,1) 到 Offset(0, 0) 进行动画 Offset( dx, dy )。(通常我们会使用 Offset.zero 构造器。)在这个情况下,对于 ‘FractionalTranslation’ widget 来说偏移量是一个 2D 矢量值。将 dy 参数设为 1,这代表在竖直方向上切换整个页面的高度。
// 动画Map表
Map tweenObj = {
'rightIn' : Tween( begin: Offset(1,0), end: Offset.zero ),
'leftIn' : Tween( begin: Offset(-1,0), end: Offset.zero ),
'topIn' : Tween( begin: Offset(0,-1), end: Offset.zero ),
'bottomIn' : Tween( begin: Offset(0,1), end: Offset.zero ),
};
1.5.3 使用 AnimatedWidget
Flutter 有一堆继承自 AnimatedWidget
的 widget,它们能够在动画的值发生改变时自动重建自己。举个例子,SlideTransition
拿到一个 Animation<Offset>
并在动画改变时使用 FractionalTranslation widget
转换其子级。
AnimatedWidget
返回了一个带有 Animation<Offset>
的 SlideTransition
,以及 child widget:
// 生成一个从右边进入的动画
var offsetAnimation = animation.drive(tweenObj['rightIn']);
1.5.4 使用 CurveTween
Curves 类提供了一个提前定义的用法相似的 curves
。例如,Curves.easeOut
将会让动画开始很快结束很慢。
var curve = Curves.ease;
var curveTween = CurveTween(curve: curve);
1.5.5 结合两个 Tween
连接两个 Tween
需要用 chain()
:
var curve = Curves.ease;
var tween = tweenObj['rightIn'].chain(CurveTween(curve: curve));
1.5.6 完整的项目代码实例 -- 封装自定义路由跳转函数
// 引入项目包
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
// 路由跳转公共函数
// ignore: missing_return
Future<Null> jumpPage(BuildContext context, String method, Widget nextPage, String animate){
// 动画Map表
Map tweenObj = {
'rightIn' : Tween( begin: Offset(1,0), end: Offset.zero ),
'leftIn' : Tween( begin: Offset(-1,0), end: Offset.zero ),
'topIn' : Tween( begin: Offset(0,-1), end: Offset.zero ),
'bottomIn' : Tween( begin: Offset(0,1), end: Offset.zero ),
};
// 定义 PageRouteBuilder 函数
Route _createRoutee(Tween tween){
// 利用 PageRouteBuilder 来创建 Route
return PageRouteBuilder(
// 设置路由基本信息Settings,可通过它获取当前路由、上个页面路由
settings: new RouteSettings(
name: '$nextPage'
),
// 用来生成要显示的Widget
pageBuilder: (context,animation,secondaryAnimation) => nextPage,
// 设置转场动画的效果
transitionsBuilder: (context, animation, secondaryAnimation, child){
return SlideTransition(
position: animation.drive(
tween.chain(CurveTween( curve: Curves.ease))
),
child: child
);
},
// 设置了动画时长
transitionDuration:const Duration(milliseconds: 600)
);
}
switch(method){
// 将设置的router信息推送到Navigator上,实现页面跳转。
case 'push':
Navigator.push(context, _createRoutee(tweenObj[animate]));
break;
// 路由替换。
case 'pushReplacement':
Navigator.pushReplacement(context, _createRoutee(tweenObj[animate]));
break;
// 从Navigator中删除路由,同时执行Route.dispose操作。
case 'removeRoute':
Navigator.removeRoute(context, _createRoutee(tweenObj[animate]));
break;
// 将给定路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
case 'pushAndRemoveUntil':
Navigator.pushAndRemoveUntil(context, _createRoutee(tweenObj[animate]), (route) => false);
break;
// 从Navigator中删除路由,同时执行Route.dispose操作。
case 'removeRoute':
Navigator.removeRoute(context, _createRoutee(tweenObj[animate]));
break;
// 导航到新页面,或者返回到上个页面。
case 'pop':
Navigator.pop(context,_createRoutee(tweenObj[animate]));
break;
}
}
1.5.7 项目中使用封装的路由跳转函数做跳转
在需要用到跳转的组件中引入封装的文件,并使用跳转函数 jumpPage
// 引入依赖包
import 'package:new_flutter/utils/util.dart'; // 封装的路由跳转函数的文件
import 'package:new_flutter/pages/next_page.dart'; // 需要跳转到的组件
onPressed: () async{
/*
* push 路由跳转的方法名
* NextPage 需要跳转的下一个路由组件
* topIn 需要用到的路由动画名
*/
jumpPage(context, 'push', NextPage(), 'topIn');
},
1.6 路由监听 RouteObserver
当页面路由进行路由跳转时,RouteObserver
就会通知订阅者。例如,每当用户从当前页面路线导航到另一个页面路线时,RouteObserver<PageRoute>
都会通知订阅的 RouteAware
对其 Route
状态的更改。
在项目中添加全局路由监听
在 main.dart
文件中添加全局路由监听逻辑:
1、将 RouteObserver 注册为全局导航观察器
// 全局的路由监听者,可在需要的widget中添加,应该放到一个全局定义的文件中
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
2、定义路由监听类
class MyRouterLister extends NavigatorObserver{
@override
// push 路由监听
void didPush(Route route, Route previousRoute) {
super.didPush(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('添加路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// pop 路由监听
void didPop(Route route, Route previousRoute) {
super.didPop(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('pop路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// remove 路由监听
void didRemove(Route route, Route previousRoute) {
super.didRemove(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('remove路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// replace 路由监听
void didReplace({Route newRoute,Route oldRoute}) {
super.didReplace();
var oldName = '';
if (oldRoute == null) {
oldName = 'null';
}else {
oldName = oldRoute.settings.name;
}
print('replace路由:' + newRoute.settings.name + ' old:' + oldName);
}
}
3、把 routeObserver 这个东西传递给 MaterialApp 的 navigatorObservers ( 其实最终给到了 Navigator , MaterialApp widget 只是包裹了 Navigator )
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
final router = Router();
MaterialApp(
title: 'Flutter App', // 设备用于为用户识别应用程序的单行描述
theme: ThemeData( // 应用程序小部件使用的颜色。
primaryColor: Color.fromRGBO(222, 225, 231, 1)
),
home: PageOne(), // 首页
navigatorKey: navigatorKey, // 在构建导航器时使用的键
onGenerateRoute: router.generator, // 应用程序导航到指定路由时使用的路由生成器回调
navigatorObservers: [MyRouterLister(),routeObserver]
);
路由监听完整 main.dart
文件代码
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:new_flutter/router/router.dart'; // 路由配置文件
import 'package:new_flutter/pages/page_one.dart'; // PageOne 组件文件
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
@override
Widget build(BuildContext context) {
//路由初始化
final router = Router();
// 全局的路由监听者,可在需要的widget中添加,应该放到一个全局定义的文件中
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
// Routers 为路由配置文件中的类
Routers.configRouters(router);
Application.router = router;
return new MaterialApp(
title: 'Flutter App', // 设备用于为用户识别应用程序的单行描述
theme: ThemeData( // 应用程序小部件使用的颜色。
primaryColor: Color.fromRGBO(222, 225, 231, 1)
),
color: Colors.red, // 在操作系统界面中应用程序使用的主色。
home: PageOne(), // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
navigatorKey: navigatorKey, // 在构建导航器时使用的键
onGenerateRoute: Application.router.generator, // 应用程序导航到指定路由时使用的路由生成器回调
navigatorObservers: [MyRouterLister(),routeObserver],
);
}
}
class MyRouterLister extends NavigatorObserver{
@override
// push 路由监听
void didPush(Route route, Route previousRoute) {
super.didPush(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('添加路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// pop 路由监听
void didPop(Route route, Route previousRoute) {
super.didPop(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('pop路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// remove 路由监听
void didRemove(Route route, Route previousRoute) {
super.didRemove(route, previousRoute);
var previousName = '';
if (previousRoute == null) {
previousName = 'null';
}else {
previousName = previousRoute.settings.name;
}
print('remove路由:' + route.settings.name + ' Previous:' + previousName);
}
@override
// replace 路由监听
void didReplace({Route newRoute,Route oldRoute}) {
super.didReplace();
var oldName = '';
if (oldRoute == null) {
oldName = 'null';
}else {
oldName = oldRoute.settings.name;
}
print('replace路由:' + newRoute.settings.name + ' old:' + oldName);
}
}
// 路由静态化
class Application {
static Router router;
}
注意: 当我们使用自定义路由( PageRouteBuilder
)的时候,若有拿取路由栈中路由信息时,必须要设置 Settings
,否则会出现路由丢失,路由信息都为 Null
1.7 Navigator
路由方法总结
Navigator
是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator
通过一个栈来管理活动路由集合
push 将设置的router信息推送到Navigator上,实现页面跳转。
of 主要是获取Navigator最近的Widget。
pop 导航到新页面,或者返回到上个页面。
canPop 判断是否可以导航到新页面
maybePop 可能会导航到新页面
popAndPushNamed 指定一个路由路径,并导航到新页面。
popUntil 反复执行pop 直到该函数的参数predicate返回true为止。
pushAndRemoveUntil 将给定路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
pushNamedAndRemoveUntil 将命名路由推送到Navigator,删除先前的路由,直到该函数的参数predicate返回true为止。
pushNamed 将命名路由推送到Navigator。
pushReplacement 路由替换。
pushReplacementNamed 替换路由操作。推送一个命名路由到Navigator,新路由完成动画之后处理上一个路由。
removeRoute 从Navigator中删除路由,同时执行Route.dispose操作。
removeRouteBelow 从Navigator中删除路由,同时执行Route.dispose操作,要替换的路由是传入参数anchorRouter里面的路由。
replace 将Navigator中的路由替换成一个新路由。
replaceRouteBelow 将Navigator中的路由替换成一个新路由,要替换的路由是是传入参数anchorRouter里面的路由。