最近用Flutter开发,发现Flutter的命名路由虽然方便,但并不适合大型项目,当你的界面越来越多,你会发现越来越难进行路由管理和维护。
试了下Fluro这个框架,虽然是解决了维护难的问题,但上手实在过于繁杂,且作者的文档寥寥无几,更多的是大神们自己的经验分享,于是便自己操刀,将路由封装成自己想要的样子。
整体封装的思维
按照我的习惯,我更喜欢Vue的路由管理,所以将仿照Vue的路由来进行封装,主要实现:
- map表形式的路由管理
- 支持嵌套路由
- 自定义路由动画
- 路由传参
实现-map表形式的路由管理
官方的路由表代码为:
//注册路由表
routes:{
"new_page":(context) => NewRoute(),
... // 省略其它路由注册信息
} ,
查看方法得知,需要返回Map<String, WidgetBuilder>
便可以新建方法Router,返回对应的格式
Map<String,Widget Function(BuildContext)> Router(BuildContext context){}
再新建方法Routes,用作路由表
List<Map<String,dynamic>> Routes(){
return [
{
"path":"/",
"view":Home(),
},
{
"path":"/changeLanguage",
"view":ChangeLanguage(),
},
]
}
然后在Router方法中,进行遍历,将路由表格式化成需要的格式进行输出
Map<String,Widget Function(BuildContext)> Router(BuildContext context){
Map<String,Widget Function(BuildContext)> router = {};
for(var route in Routes()){
router[route["path"]] = (context)=>route["view"];
}
return router;
}
最后将main.dart中的routes替换成我们的方法即可
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: Router(context)
);
}
}
实现-支持嵌套路由
简单的路由表实现了,但这样和官方的命名路由方式其实一样,只是变成了表的形式,为了拓展,我们需要实现嵌套的功能
在Routes中添加子路由
List<Map<String,dynamic>> Routes(){
return [
{
"path":"/",
"view":Home(),
},
//嵌套路由示例
{
//上层路由无需view
"path":"/busManage",
"children":[
//匹配规则为"/busManage/demo1"
{
"path":"/demo1",
"view":Demo1()
},{
//匹配规则为"/busManage/demo2/demo3"
"path":"/demo2",
"children":[
{
"path":"/demo3",
"view":Demo3()
}
]
},
]
}
];
}
此时观察我们的结构,children的结构和父级结构一致,我们便可以使用递归的方式来实现。
更改Router的方法:
Map<String,Widget Function(BuildContext)> Router(BuildContext context){
Map<String,Widget Function(BuildContext)> router = {};
List<Map<String,dynamic>> screenRoutes = [];
for(var route in Routes()){
var path = route["path"];
if(route["children"]!=null){
TreeChildrenRouter(path, route["children"],screenRoutes);
}else{
router[route["path"]] = (context)=>route["view"];
}
}
for(var route in screenRoutes){
router[route["path"]] = (context)=>route["view"];
}
return router;
}
TreeChildrenRouter(String path,List<Map> routes,List<Map<String,dynamic>> screenRoutes){
String rootPath = path;
for(var route in routes){
path=rootPath+route["path"];
if(route["children"]!=null){
TreeChildrenRouter(path, route["children"], screenRoutes);
}else{
screenRoutes.add({"path":path,"view":route["view"]});
}
}
}
实现-自定义路由动画
基本仿Vue的路由实现了,但是有时候业务需要我们做各种各样的动效,包括路由的动效,而官方的命名路由是不支持自定义动画路由的,只能通过模态路由才能实现自定义动画,我们当然不能屈服,我们可以通过新建工具类,来实现命名路由转换成模态路由,从而实现自定义动画。
新建AnimateRouter继承PageRouteBuilder实现动画模态路由
class AnimateRouter extends PageRouteBuilder {
final Widget x;
final String mode;
AnimateRouter(this.x, this.mode)
: super(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (BuildContext context, Animation<double> animation1,
Animation<double> animation2) {
return x;
},
transitionsBuilder: (BuildContext context,
Animation<double> animation1,
Animation<double> animation2,
Widget child) {
Widget routerWidget;
switch (mode) {
case 'slide':
routerWidget = SlideTransition(
child: child,
position: Tween<Offset>(
begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))
.animate(CurvedAnimation(
parent: animation1, curve: Curves.ease)),
);
break;
case 'fade':
routerWidget = FadeTransition(
child: child,
opacity: Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animation1, curve: Curves.linear)),
);
break;
case 'scale':
routerWidget = ScaleTransition(
child: child,
scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: animation1, curve: Curves.linear)),
);
break;
case 'rotation':
routerWidget = RotationTransition(
child: child,
turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: animation1, curve: Curves.linear)),
);
break;
case 'rotationScale':
routerWidget = RotationTransition(
child: ScaleTransition(
child: child,
scale: Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animation1, curve: Curves.linear)),
),
turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: animation1, curve: Curves.linear)),
);
break;
}
return routerWidget;
});
}
其中的x便是我们的需要传入的路由组件,mode是路由动画的判断key,根据mode来控制路由做哪个变换动画
新建RouterUtil来实现命名路由转换成动画模态路由,思路就是从我们自定义的路由表获取到对应的路由组件,传入模态路由即可,其中的argument是路由传参,下节会说。
class RouterUtil {
RouterUtil.pushNamed(BuildContext context, String name,
{String mode = 'slide', dynamic argument = ''}) {
Map<String, Widget Function(BuildContext)> routerList = Router(context);
if (routerList.containsKey(name)) {
setArgument(argument);
Navigator.of(context)
.push(AnimateRouter(routerList[name](context), mode));
} else {
throw FormatException("未寻找到命名路由");
}
}
RouterUtil.pushReplacementNamed(BuildContext context, String name,
{String mode = 'slide', dynamic argument = ''}) {
Map<String, Widget Function(BuildContext)> routerList = Router(context);
if (routerList.containsKey(name)) {
setArgument(argument);
Navigator.of(context)
.pushReplacement(AnimateRouter(routerList[name](context), mode));
} else {
throw FormatException("未寻找到命名路由");
}
}
RouterUtil.pushNamedAndRemoveUntil(BuildContext context, String name,
{String mode = 'slide', dynamic argument = ''}) {
Map<String, Widget Function(BuildContext)> routerList = Router(context);
if (routerList.containsKey(name)) {
setArgument(argument);
Navigator.of(context).pushAndRemoveUntil(
AnimateRouter(routerList[name](context), mode), (route) => false);
} else {
throw FormatException("未寻找到命名路由");
}
}
}
实现-路由传参
有了上面的路由封装,会发现有个致命的缺陷,我们的自定义动画路由没法传参。官方的模态路由传参是在路由组件中进行的,而我们的封装方式无法进行这种操作,于是另辟蹊径,使用get方式来实现
在RouterUtil方法的外层,命名变量argument
dynamic _argument;
dynamic get argument => _argument;
setArgument(dynamic argument) {
_argument = argument;
}
这样在路由组件中,直接获取argument便是我们传的参数了。
结语
以上实践来源于Flutter Create Framework,这是一个Flutter的快速开发框架,是一个基于实际的业务需求而形成的Template
国内仓库:gitee
国外仓库:github
开发文档:wiki