Routemaster 
您好!Routemaster 是一个易于使用的 Flutter 路由器,它封装了 Navigator 2.0……并且有一个傻名字。
功能
- 将 URL 映射到页面,简单声明式
- 易于使用的 API:只需
Routemaster.of(context).push('/page') - 对标签页的嵌套导航支持非常简单
- 多个路由映射:例如一个用于已登录用户,另一个用于未登录用户
- 观察者,可轻松监听路由变化
- 由 250 多个单元、组件和集成测试覆盖
这是应用程序中标签页和推送路由所需的所有路由设置
final routes = RouteMap(
routes: {
'/': (_) => CupertinoTabPage(
child: HomePage(),
paths: ['/feed', '/settings'],
),
'/feed': (_) => MaterialPage(child: FeedPage()),
'/settings': (_) => MaterialPage(child: SettingsPage()),
'/feed/profile/:id': (info) => MaterialPage(
child: ProfilePage(id: info.pathParameters['id'])
),
}
);
void main() {
runApp(
MaterialApp.router(
routerDelegate: RoutemasterDelegate(routesBuilder: (context) => routes),
routeInformationParser: RoutemasterParser(),
),
);
}
然后进行导航
Routemaster.of(context).push('/feed/profile/1');
……您可以在这个简单的应用程序示例中看到它的实际效果。
还有一个更高级的示例。
我非常乐意收到您的任何反馈!请为 API 反馈创建一个 issue。
文档
从下面的快速入门开始,同时也可以查看API 参考、Wiki 和常见问题解答。
快速入门 API 导览
概述
Routemaster 根据当前路径生成页面。这是其基于路径的路由的核心概念。路径结构很重要。
它使用路径来决定页面的推送位置。这意味着路径需要与您期望的页面层次结构匹配。
例如
'/tabs': (route) => TabPage(child: HomePage(), paths: ['one', 'two']),
// First tab default page
'/tabs/one': (route) => MaterialPage(child: TabOnePage()),
// Second tab default page
'/tabs/two': (route) => MaterialPage(child: TabTwoPage()),
// Second tab sub-page: will be displayed in the 2nd tab because it
// starts with '/tabs/two'
'/tabs/two/subpage': (route) => MaterialPage(child: TabTwoPage()),
// Not a tab page: will not be displayed in in a tab
// because the path doesn't start with '/tabs/one' or '/tabs/two'
'/tabs/notInATab': (route) => MaterialPage(child: NotTabPage()),
任何以 /tabs/one 或 /tabs/two 开头的子路径都将被推送到正确的标签页中。
当导航到 /tabs/two/subpage 时,TabPage 会被问到“嘿,你知道如何处理这个路径吗?”它会回答“当然!它以 /tabs/two 开头,所以它会进入我的第二个标签页”。
但是,导航到 /tabs/notInATab 将 **不会** 显示在标签页中,而是会推送到标签栏的顶部。
TabPage 会说“是的,抱歉,我不知道如何处理这个,它不匹配我的任何标签页路径”,然后它的父级会被要求处理它。
路径层次结构很重要,例如更改对话框的显示位置。
路由
基础应用路由设置
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
routesBuilder: (context) => RouteMap(routes: {
'/': (routeData) => MaterialPage(child: PageOne()),
'/two': (routeData) => MaterialPage(child: PageTwo()),
}),
),
routeInformationParser: RoutemasterParser(),
)
从页面内部导航
Routemaster.of(context).push('relative-path');
Routemaster.of(context).push('/absolute-path');
Routemaster.of(context).replace('relative-path');
Routemaster.of(context).replace('/absolute-path');
路径参数
// Path '/products/123' will result in ProductPage(id: '123')
RouteMap(routes: {
'/products/:id': (route) => MaterialPage(
child: ProductPage(id: route.pathParameters['id']),
),
'/products/myPage': (route) => MaterialPage(MyPage()),
})
请注意,没有路径参数的路由具有更高的优先级,因此在上面的例子中
/products/myPage 将显示 MyPage。
查询参数
// Path '/search?query=hello' results in SearchPage(query: 'hello')
RouteMap(routes: {
'/search': (route) => MaterialPage(
child: SearchPage(query: route.queryParameters['query']),
),
})
在 Widget 中获取当前路径信息
RouteData.of(context).path; // Full path: '/product/123?query=param'
RouteData.of(context).pathParameters; // Map: {'id': '123'}
RouteData.of(context).queryParameters; // Map: {'query': 'param'}
标签页
设置
RouteMap(
routes: {
'/': (route) => TabPage(
child: HomePage(),
paths: ['/feed', '/settings'],
),
'/feed': (route) => MaterialPage(child: FeedPage()),
'/settings': (route) => MaterialPage(child: SettingsPage()),
},
)
主页面
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tabPage = TabPage.of(context);
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: tabPage.controller,
tabs: [
Tab(text: 'Feed'),
Tab(text: 'Settings'),
],
),
),
body: TabBarView(
controller: tabPage.controller,
children: [
for (final stack in tabPage.stacks) PageStackNavigator(stack: stack),
],
),
);
}
}
Cupertino 风格的标签页
设置
RouteMap(
routes: {
'/': (route) => CupertinoTabPage(
child: HomePage(),
paths: ['/feed', '/settings'],
),
'/feed': (route) => MaterialPage(child: FeedPage()),
'/settings': (route) => MaterialPage(child: SettingsPage()),
},
)
主页面
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tabState = CupertinoTabPage.of(context);
return CupertinoTabScaffold(
controller: tabState.controller,
tabBuilder: tabState.tabBuilder,
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
label: 'Feed',
icon: Icon(CupertinoIcons.list_bullet),
),
BottomNavigationBarItem(
label: 'Settings',
icon: Icon(CupertinoIcons.search),
),
],
),
);
}
}
受保护的路由
如果验证失败,则显示默认的未找到页面
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: NotFound()
如果验证失败,则重定向到另一页面(会更改 URL)
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: Redirect('/no-access'),
如果验证失败,则显示另一页面(不更改 URL)
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: MaterialPage(child: CustomNoAccessPage())
404 页面
未知 URL 时显示的默认页面
RouteMap(
onUnknownRoute: (route, context) {
return MaterialPage(child: NotFoundPage());
},
routes: {
'/': (_) => MaterialPage(child: HomePage()),
},
)
重定向
将一个路由重定向到另一个路由
RouteMap(routes: {
'/one': (routeData) => MaterialPage(child: PageOne()),
'/two': (routeData) => Redirect('/one'),
})
将所有路由重定向到登录页面,用于未登录状态的路由映射
RouteMap(
onUnknownRoute: (_) => Redirect('/'),
routes: {
'/': (_) => MaterialPage(child: LoginPage()),
},
)
将路径参数从原始路径传递到重定向路径
RouteMap(routes: {
'/user/:id': (routeData) => MaterialPage(child: UserPage(id: id)),
'/profile/:uid': (routeData) => Redirect('/user/:uid'),
})
交换路由映射
您可以在运行时交换整个路由映射。
这对于根据用户是否登录而显示不同页面特别有用
final loggedOutMap = RouteMap(
onUnknownRoute: (route, context) => Redirect('/'),
routes: {
'/': (_) => MaterialPage(child: LoginPage()),
},
);
final loggedInMap = RouteMap(
routes: {
// Regular app routes
},
);
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
routesBuilder: (context) {
// This will rebuild when AppState changes
final appState = Provider.of<AppState>(context);
return appState.isLoggedIn ? loggedInMap : loggedOutMap;
},
),
routeInformationParser: RoutemasterParser(),
);
导航观察者
class MyObserver extends RoutemasterObserver {
// RoutemasterObserver extends NavigatorObserver and
// receives all nested Navigator events
@override
void didPop(Route route, Route? previousRoute) {
print('Popped a route');
}
// Routemaster-specific observer method
@override
void didChangeRoute(RouteData routeData, Page page) {
print('New route: ${routeData.path}');
}
}
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
observers: [MyObserver()],
routesBuilder: (_) => routeMap,
),
routeInformationParser: RoutemasterParser(),
);
在没有 context 的情况下导航
app.dart
final routemaster = RoutemasterDelegate(
routesBuilder: (context) => routeMap,
);
MaterialApp.router(
routerDelegate: routemaster,
routeInformationParser: RoutemasterParser(),
)
my_widget.dart
import 'app.dart';
void onTap() {
routemaster.push('/blah');
}
Hero 动画
Hero 动画将在顶级导航器上自动工作(假设您使用的是 MaterialApp 或 CupertinoApp)。
对于任何子导航器,您需要将 PageStackNavigator 包装在 HeroControllerScope 中,如下所示
HeroControllerScope(
controller: MaterialApp.createMaterialHeroController(),
child: PageStackNavigator(
stack: pageStack,
)
)
设计目标
- 集成:与 Flutter Navigator 2.0 API 集成,不试图取代它。尝试具有非常 Flutter 风格的 API。
- 可用:围绕用户场景/故事进行设计,例如Flutter storyboard 中的场景——此处有示例。
- 有主见:不提供 10 种方法来达成一个目标,而是为所有场景提供灵活性。
- 专注:只关注导航,仅此而已。例如,不包含依赖项注入。
这个项目建立在 page_router 之上。
名称
(照片由 Chris Sampson 拍摄,根据 CC BY 2.0 许可)