目录
前言
在 Flutter 中,App 多个页面之间的跳转是由 Navigator(导航器)来管理的,如常见的 Navigator.push 跳转到下一页,Navigator.pop 回到上一页,同时也会涉及到页面之间的参数传递。本文主要介绍一下动态路由、静态路由及第三方路由插件 Fluro,它们在页面跳转、参数传递的区别和各自的优缺点,日常开发选择合适的即可。
动态路由
动态路由是不需要显示声明的,通过代码来实现,如下面这样的:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "FlutterToDo",
theme: ThemeData(
brightness: Brightness.dark,
),
// 直接将 HomePage() 赋值给 MaterialApp 的 home
home: HomePage(),
);
}
}
页面跳转通过 Navigator.push,在跳转到 SecondPage.dart 页面时,如果有参数时需要通过构造函数将参数传递给 SecondPage.dart,如这里的参数是 pageTitle。
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FlutterToDo"),
),
body: Center(
child: RaisedButton(
child: Text("这是第一个界面,点击进入第二个界面"),
onPressed: () {
// 点击跳转页面,无参数
// Navigator.push
// context,
// MaterialPageRoute(
// builder: (context) => SecondPage(),
// ));
// 带参数的页面跳转
Navigator.push(
context,
MaterialPageRoute(
// 参数:pageTitle
builder: (context) => SecondPage(pageTitle: "SecondPage"),
// 调用 then 等待接收 SecondPage 返回的数据
)).then((value) => print(value));
},
},
),
),
);
}
}
SecondPage.dart页面代码,参数 pageTitle依赖接收上个页面传递过来的数据并直接作为当前页面的标题。
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
final String pageTitle;
SecondPage({Key key, this.pageTitle = "第二个界面"});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(pageTitle),
),
body: Center(
child: RaisedButton(
child: Text("这是第二个界面,点击进入第一个界面"),
onPressed: () {
// 返回上一个界面,
// Navigator.pop(context);
// 携带参数上一个界面,"SecondPage pop" 为传递给上一个页面的参数。
Navigator.pop(context, "SecondPage pop");
},
),
),
);
}
}
动态路由简单直接,传参时参数名称定义明确,调用者通过构造函数就能提示具体参数,使用起来比较方便,不用特地的去声明路由,缺点是缺乏统一管理,当前页面需要导入要跳转的页面,页面之间有强依赖,项目后期业务逻辑增多、结构变复杂,维护成本也就增加,不利于业务模块组件化。
静态路由
静态路由又称命名路由,其实现方式是在跳转之前,需要通过在MaterialApp内的routes属性内显式声明路由的名称,代码实现如下:
import 'package:flutter/material.dart';
import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "FlutterToDo",
// 默认加载的页面
initialRoute: '/',
// 显式声明路由列表
routes: {
'/': (context) => HomePage(),
'/secondPage': (context) => SecondPage(),
},
);
}
}
此时首页的代码实现及点击按钮的跳转逻辑如下:
// 跳转到第二个界面,
Navigator.pushNamed(context, '/secondPage');
这里的 /secondPage 就是上面 MyApp - > MaterialApp-> routes 中定义的路由,名称必须保持一致。SecondPage 页面点击按钮返回:
// 返回上一个界面
Navigator.pop(context);
静态路由传参
静态路由是通过 MyApp - > MaterialApp-> onGenerateRoute 属性来处理。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "FlutterToDo",
initialRoute: '/', // 默认界面
// 当页面跳转时进行参数处理
onGenerateRoute: (RouteSettings settings) {
// ......
},
);
}
}
查看 onGenerateRoute 的源码定义可以看到,根据所给的 route settings 来返回一个 Route<dynamic>。
而打开 RouteSettings 类的实现看到其构造函数有 name 和 arguments,其中 name 为路由名称,arguments 就是传递的参数,其类型为 Object。
将静态路由的代码优化一下之后:
import 'package:flutter/material.dart';
import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// routes中声明所有的页面
final routes = {
'/': (context, {arguments}) => HomePage(),
'/secondPage': (context, {arguments}) => SecondPage(arguments: arguments),
};
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "FlutterToDo",
initialRoute: '/', // 默认界面
// 当页面跳转时进行参数处理
onGenerateRoute: (RouteSettings settings) {
// 1. 根据路由名称从上面声明的 routes 取出类似于 (context, {arguments}) => HomePage()的 pageBuilder函数体
var pageBuilder = routes[settings.name];
if (pageBuilder != null) {
// 2. 判断是否有携带的参数
if (settings.arguments != null) {
// 创建路由页面并携带参数
// 3. 传递 context 和 settings.arguments给 pageBuilder 函数体执行,
// 并返回一个Widget页面给到 MaterialPageRoute 的 builder
return MaterialPageRoute(
builder: (context) =>
pageBuilder(context, arguments: settings.arguments));
} else {
// 3. 无参数的情况
return MaterialPageRoute(
builder: (context) => pageBuilder(context));
}
}
// 默认返回 HomePage页面
return MaterialPageRoute(builder: (context) => HomePage());
},
);
}
}
在 HomePage 点击按钮的跳转和传参:
// 通过arguments指定参数
Navigator.pushNamed(context, "/secondPage", arguments: {'title': "SecondPage title"}).then((value) {
// 退出 SecondPage 时返回的参数
print(value) // 返回传递数据 SecondPage pop
});
此时 SecondPage 页面获取参数代码如下:
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
final Map arguments;
SecondPage({Key key, this.arguments});
@override
Widget build(BuildContext context) {
String title = arguments != null ? arguments['title'] : "SecondPage";
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: RaisedButton(
child: Text("这是第二个界面,点击进入第一个界面"),
onPressed: () {
// 返回上一个界面
Navigator.pop(context, "返回传递数据 SecondPage pop");
},
),
),
);
}
}
从上面可以看出,静态路由及传参优点也很明显,声明路由统一在一个位置定义,方便管理,当然也可以创建一个管理类(如:RouteManager)将 routes 中的路由 path 声明为静态的变量。
class RouteManager {
static String homePage = "/";
static String secondPage = "/secondPage";
static routes = {
homePage: (context, {arguments}) => HomePage(),
secondPage: (context, {arguments}) => SecondPage(arguments: arguments),
};
}
此时直接使用 RouteManager.secondPage 就可以了,相比于动态路由,跳转时不再需要导入 SecondPage,页面直接的耦合度降低。缺点通过 arguments 不够直接准确,同时在 SecondPage也需要定义一个 Map 来接收,这里的Map结构就需要调用者和使用者都能明确且准确无误填入参数字段,根据参数字段取出数据,对调用者和使用者都有一定的要求,同时也非常容易出错。
Fluro 实现路由导航与传参
第三方插件 fluro 在 pub地址,接下来,我们通过代码来看看 fluro 是如何解决 动态路由带来的页面耦合度过高和静态路由传参不方便的问题。
在pubspec.yaml中导入插件:
fluro: ^2.0.3
此时 MyApp 代码:
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key) {
MyRoutes.configureRoutes();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 技术实践',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: MyRoutes.router.generator,
initialRoute: MyRoutes.root,
);
}
}
MyRoutes 的实现实际上就是封装的 FluroRouter,路由的定义也统一在这个类里面完成,其实现如下:
import 'package:fluro/fluro.dart';
class MyRoutes {
static FluroRouter router = FluroRouter();
static String testRoutePage = "/testRoutePage";
static String secondPage = "/testRoutePage/secondPage";
static void configureRoutes() {
router.notFoundHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return const Text("ROUTE WAS NOT FOUND !!!");
});
router.define(testRoutePage,
handler: Handler(handlerFunc: (_, __) => const TestRoutePage()));
router.define(secondPage,
handler: Handler(handlerFunc: (_, params) {
String title = params['title']?.first ?? "";
return SecondPage(title: title,);
}));
}
}
TestRoutePage 跳转到 SecondPage 并传递 title 参数。
class TestRoutePage extends StatelessWidget {
const TestRoutePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: const Text("TestRoutePage"),
onPressed: () {
// 跳转到 SecondPage,传参方式和get请求很像
MyRoutes.router
.navigateTo(context, MyRoutes.secondPage + "?title=hello")
.then((value) => print(value));
},
),
),
);
}
}
SecondPage 页面实现
class SecondPage extends StatelessWidget {
String? title;
SecondPage({Key? key, this.title = "第二个页面"}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text(title ?? ""),
onPressed: () {
Navigator.pop(context, "SecondPage pop 参数");
},
),
),
);
}
}
相比较于动态路由和静态路由,Fluro 传参方式更加简洁,运行在Web上时,而且通用性更高,当然还有更多好用的功能,这里只是简单的介绍 Fluro 的基本使用,详细用法请移步查看 Fluro 相关文档。
小结:日常开发中,路由导航和传参使用频率非常之高,选择合适的路由管理方式不仅有利于项目的维护和扩展,后期如果需要兼容Web等平台时,也能胜任。
本文代码可直接打开 flutter_todo 查看效果,同步更新到同名微信公众号(公众号搜索:flutter_todo)。