这里给你一套最常用的用法速查 + 代码模板,直接抄就能跑。
概念速览
-
fluro 是一个轻量的 Flutter 路由库,支持路径参数、查询参数、自定义转场动画、404 页面等。
-
v2 开始类名叫 FluroRouter(避免和 Flutter 自带 Router 混淆)。
安装
-
pubspec.yaml 增加依赖:fluro(版本请看 pub.dev 最新)
初始化(建议单独建个 router.dart)
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
// import your pages here
class AppRouter {
static final FluroRouter router = FluroRouter();
static void setup() {
// 404
router.notFoundHandler = Handler(
handlerFunc: (context, params) => const Scaffold(
body: Center(child: Text('404 Not Found')),
),
);
// 首页
router.define(
'/',
handler: Handler(handlerFunc: (_, __) => const HomePage()),
);
// 动态路径参数 + 可选查询参数
router.define(
'/user/:id',
handler: Handler(handlerFunc: (context, params) {
final id = params['id']?.first ?? '';
final name = params['name']?.first; // ?name=xxx
return UserPage(id: id, name: name);
}),
transitionType: TransitionType.cupertino,
);
// 传复杂字符串:先 encode,再在页面里 decode
router.define(
'/webview/:url',
handler: Handler(handlerFunc: (context, params) {
final url = Uri.decodeComponent(params['url']!.first);
return WebViewPage(url: url);
}),
);
// 用于演示返回值
router.define(
'/picker',
handler: Handler(handlerFunc: (_, __) => const PickerPage()),
);
}
}
在 MaterialApp 中接管路由
void main() {
AppRouter.setup();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fluro Demo',
onGenerateRoute: AppRouter.router.generator,
);
}
}
常用操作
-
跳转(路径参数 + 查询参数)
// /user/:id + ?name=xxx AppRouter.router.navigateTo( context, '/user/123?name=${Uri.encodeComponent("张三")}', );
-
自定义转场动画
AppRouter.router.navigateTo( context, '/picker', transition: TransitionType.custom, transitionBuilder: (ctx, anim, sec, child) => FadeTransition(opacity: anim, child: child), transitionDuration: const Duration(milliseconds: 250), );
-
替换当前页 / 清空栈(比如登录完成进首页)
AppRouter.router.navigateTo( context, '/home', replace: true, clearStack: true, );
-
接收返回值
final result = await AppRouter.router.navigateTo(context, '/picker'); // 在 PickerPage 里用 Navigator.of(context).pop('选中的值');
-
传 URL/中文等复杂字符串
final url = Uri.encodeComponent('https://flutter.dev?q=中文'); AppRouter.router.navigateTo(context, '/webview/$url');
下面按“概念 → 匹配机制 → API 细节 → 进阶用法/坑位”的顺序,把 fluro 路由再讲细一点,配上实用代码片段,拿去即用。
一、核心概念
-
FluroRouter:路由中枢,负责注册和匹配,挂到 MaterialApp.onGenerateRoute。
-
Handler:某条路由被匹配后怎么处理。handlerFunc 返回要展示的 Widget(或做函数式处理)。
-
params:传给 handlerFunc 的参数表,类型为 Map<String, List<String>>,包含路径参数和查询参数。
-
TransitionType:转场枚举(native/material/cupertino/inFromRight/inFromLeft/inFromBottom/fadeIn/custom 等)。
-
notFoundHandler:没有匹配到任何路由时的兜底页面。
二、路由匹配机制(怎么匹配到你的页面)
-
以斜杠分段匹配:/user/123 会依次匹配 /、user、123 这三个段。
-
动态段用 :param 表示,只匹配单段:
-
/user/:id 可以匹配 /user/123
-
只能匹配单段值,不能直接吃掉多段(包含斜杠)——要用 Uri.encodeComponent 先编码。
-
-
查询参数和路径参数合并进同一个 params:
-
/user/123?name=张三 → params['id']!.first '123',params['name']!.first '张三'
-
同名多值:/search?tag=a&tag=b → params['tag'] == ['a','b']
-
-
可选参数不内置语法。要么走查询参数,要么定义两条路由分别覆盖。
-
404:没有任何定义匹配,就走 notFoundHandler。
三、定义与初始化 建议独立文件集中管理路由,便于全局检索与重构。
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
class AppRouter {
static final FluroRouter router = FluroRouter();
static void setup() {
// 404
router.notFoundHandler = Handler(
handlerFunc: (_, __) => const Scaffold(
body: Center(child: Text('404 Not Found')),
),
);
// 首页
router.define(
'/',
handler: Handler(handlerFunc: (_, __) => const HomePage()),
// transitionType: TransitionType.native, // 可在定义时设默认转场
);
// 动态参数 + 查询参数
router.define(
'/user/:id',
handler: Handler(handlerFunc: (context, params) {
final id = params['id']?.first ?? '';
final name = params['name']?.first; // ?name=xxx
return UserPage(id: id, name: name);
}),
transitionType: TransitionType.cupertino,
);
// 复杂字符串(需要 encode/decode)
router.define(
'/webview/:url',
handler: Handler(handlerFunc: (_, params) {
final url = Uri.decodeComponent(params['url']!.first);
return WebViewPage(url: url);
}),
);
}
}
// 在 MaterialApp 中接管
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fluro Demo',
onGenerateRoute: AppRouter.router.generator,
);
}
}
四、跳转 API 详解(navigateTo 的常用参数)
// 基本跳转
await AppRouter.router.navigateTo(
context,
'/user/123?name=${Uri.encodeComponent("张三")}',
// 是否替换当前页(pushReplacement)
replace: false,
// 是否清空栈(pushAndRemoveUntil)
clearStack: false,
// 跳转时覆盖转场(优先级高于定义时的 transitionType)
transition: TransitionType.fadeIn,
transitionDuration: const Duration(milliseconds: 250),
// 自定义转场
transitionBuilder: (ctx, anim, secAnim, child) =>
FadeTransition(opacity: anim, child: child),
// 透传 RouteSettings(可用 ModalRoute.of(context)?.settings.arguments 拿)
routeSettings: const RouteSettings(arguments: {'from': 'home'}),
);
// 替换当前页 + 清空返回栈(典型:登录完成进首页)
AppRouter.router.navigateTo(context, '/home', replace: true, clearStack: true);
五、参数传递/获取的细节与技巧
-
路径参数:params['id']?.first
-
查询参数:params['keyword']?.first
-
多值查询:params['tag'] // List<String>
-
复杂/包含斜杠或中文的值:务必在拼接 URL 前用 Uri.encodeComponent,页面内再 decode。
-
例:/webview/${Uri.encodeComponent('https://a.com/p/1?lang=中文')}
-
-
也可通过 RouteSettings.arguments 传递对象:
AppRouter.router.navigateTo( context, '/detail/42', routeSettings: RouteSettings(arguments: MyObj(...)), ); // 页面里: final args = ModalRoute.of(context)?.settings.arguments as MyObj?;
六、转场动画的几种用法
-
路由层面默认转场:define 时的 transitionType
-
调用层面覆盖转场:navigateTo 的 transition/transitionBuilder
-
自定义动画建议统一封装函数,避免在业务里散落重复代码
七、404 与重定向
-
统一兜底页:
router.notFoundHandler = Handler( handlerFunc: (_, __) => const NotFoundPage(), );
-
重定向(两种常见姿势,二选一):
1) 在调用方判断:如果老地址就跳新地址
2) 在老路由的 handler 里重定向-
如果你的 fluro 版本支持 HandlerType.function:
router.define( '/old', handler: Handler( type: HandlerType.function, handlerFunc: (context, params) { AppRouter.router.navigateTo(context!, '/new', replace: true); return null; }, ), );
-
如果不支持,使用微任务避免首帧闪烁:
router.define('/old', handler: Handler(handlerFunc: (context, params) { Future.microtask(() => AppRouter.router.navigateTo(context!, '/new', replace: true)); return const SizedBox.shrink(); }));
-
八、鉴权/路由守卫的常见实现
-
调用方拦截(简单稳定,推荐):
Future<T?> guardGo<T>(BuildContext ctx, String path) { final authed = ctx.read<Auth>().isLoggedIn; return AppRouter.router.navigateTo(ctx, authed ? path : '/login'); }
-
页面级守卫(在 handler 内判断):
router.define('/profile', handler: Handler(handlerFunc: (ctx, params) { final authed = ctx!.read<Auth>().isLoggedIn; return authed ? const ProfilePage() : const LoginPage(); }));
-
函数路由(HandlerType.function)做“先验证再跳转”。
九、嵌套路由/多 Navigator(Tab/底部导航)
-
每个子 Navigator 仍可使用同一个 FluroRouter 的 generator:
Navigator( key: _tabNavKey, onGenerateRoute: AppRouter.router.generator, );
-
注意使用对应子 Navigator 的 context 跳转,这样返回栈不会串。
十、与 Navigator 2.0/Web 的关系
-
fluro 基于 Navigator 1.0(onGenerateRoute)。需要完全声明式/URL 同步的项目可以考虑 go_router/beamer。
-
Flutter Web 直接在地址栏输入已定义的路径能命中(onGenerateRoute 生效)。刷新不会丢失路径,但你的页面需要自行做状态恢复。
十一、常见坑位
-
BuildContext 在 handler 中是可空(BuildContext?),有些路径解析阶段可能为 null,使用前判断或尽量少依赖 context。
-
忘记对中文/带斜杠参数做 Uri.encodeComponent → 解析失败或 404。
-
只定义 :param 想匹配多段值(含 /)→ 不行,改走 encode 或改用查询参数。
-
可选参数不支持语法糖 → 用查询参数或额外定义一条路由。
-
多终端统一转场:iOS 用 cupertino,Android 用 material/native,可按平台分支或统一用自定义动画。
十二、项目结构建议
-
routes.dart:字符串常量/构造器,避免魔法字串
-
app_router.dart:集中注册 define、notFoundHandler
-
业务模块各自 pages/ 子目录
-
可选:route helpers 封装强类型跳转
class Routes { static const home = '/'; static String user(String id, {String? name}) => '/user/$id${name == null ? '' : '?name=${Uri.encodeComponent(name)}'}'; } extension RouteX on BuildContext { Future goUser(String id, {String? name}) => AppRouter.router.navigateTo(this, Routes.user(id, name: name)); }