Flutter已经成为构建跨平台应用程序的流行工具包。它支持所有主要平台,包括安卓、iOS和网络。
导航对于任何应用程序都是非常重要的。它为各种平台提供的导航API提供了一个统一的抽象。Flutter为导航提供了两种类型的API:命令式和声明式。
在本教程中,我们将介绍Flutter 1.0中使用的命令式导航方法,以及Flutter 2.0中使用的声明式方法。
我们将讨论以下内容。
- 强制性导航(Flutter 1.0
- 声明式导航(Flutter 2.0
- Flutter导航器
- 使用
RouterDelegate [RouteInformationParser](#route-information-parser)- 把它放在一起
强制导航(Flutter 1.0
Flutter 1.0 对导航采取了一种强制性的方法。在Flutter中,导航由一个小部件的堆栈组成,其中小部件被推到上面,也从上面弹出。
FlutterNavigator 类
Navigator 类提供了 Flutter 应用程序中的所有导航功能。
Navigator 提供了通过向堆栈推送或从堆栈中弹出的方法来突变堆栈。Navigator.push 方法用于导航到一个较新的页面,Navigator.pop 用于从当前页面返回。
下面是一个关于pop 和push 的基本例子:push 方法把BuildContext 作为第一个参数,第二个参数是PageBuilder 。这个例子使用了MaterialPageRoute ,它提供了过渡动画并处理路线的变化。
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'My App',
home: Main(),
));
}
class Main extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Route'),
),
body: Center(
child:RaisedButton(
child: Text('Open route'),
onPressed: () {
// pushing SecondRoute
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
),
);
}
}
pop 方法只接受BuildContext ,并改变当前路线。
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Removing SecondRoute
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
Navigator 提供了更多的方法,包括*pushReplacement* ,它的参数与push 类似。它将替换当前的路由,所以不可能导航回旧的路由。
例如,在成功登录后,你想使用*pushReplacement* ,以防止用户返回到登录界面。
命名的路由
命名路由允许你通过使用字符串来改变路径,而不是提供组件类,这反过来又使你能够重复使用代码。
命名路由在MaterialApp 上被定义为一个地图。这些路由可以从应用程序的任何部分使用。
定义路由
路由是一个带有字符串键和值的地图,例如传递给routes 属性的建设者,在MaterialApp 。
void main() {
runApp(MaterialApp(
title: 'My App',
home: Main(),
// Routes defined here
routes: {
"second":(context)=>SecondRoute()
},
));
}
使用命名的路由
代替push ,pushNamed ,用来改变到一个新的路由。类似地,*pushReplacementNamed* ,而不是pushReplacement 。pop 方法对所有的路由都是一样的。
class Main extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Route'),
),
body: Center(
child:RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.pushReplacementNamed(context, "second");
},
),
),
);
}
}
声明式导航(Flutter 2.0
Flutter 2.0带有改进的导航,这在很大程度上得益于它对声明式方法的支持。这使得路由成为状态的一个函数--也就是说,页面在状态变化时发生变化。Flutter 2.0还对网络上的导航有更好的支持。
Flutter团队为宣布Flutter导航2.0和Router而公开分享的这张图,很好地描述了这一流程。
Flutter导航器
导航器接收一个页面列表,并显示最后一个页面。你可以通过添加或删除列表末尾的页面来改变其页面。
下面的例子演示了如何使用Navigator类与新的Flutter Navigator使用基于页面的导航。
_page 是由这个类管理的状态。对于导航来说,这个_page 是在setState 调用中操作的。
class _App extends State {
// Creating state for pages
List<Page> _pages=[];
_page 被传递给Navigator类。导航器将根据_page 的值来改变当前页面。
当进行基于操作系统的导航时,如在Android上按下后退按钮等,onPopPage 。
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
onPopPage: (route,result){
// check if route removed
if(route.didPop(result)){
// remove the last page
_pages.removeLast();
return true;
}
return false;
},
pages: _pages,
),
);
}
}
初始页面可以通过在initState 生命周期方法中添加一个页面来设置。
@override
void initState() {
super.initState();
// setting intial page
_pages=[_buildMain()];
}
要创建一个新的材料页,请使用MaterialPage widget。MaterialPage 需要一个孩子和一个键。导航器使用key 来区分页面并检测页面变化。
在按下click 按钮时,一个新的页面被添加到_page 状态。setState 被调用以触发小组件的重建,Navigator 自动处理页面变化。
// This function creates a Page using MaterialPage
Page _buildMain(){
return MaterialPage(child: Scaffold(
body: Center(
child: ElevatedButton(
child: Text("click"),
onPressed: (){
// When clicked add a new Page to _page list
_pages.add(_buildSecondPage());
// call setState to trigger rebuild for Widget
setState(() {
// create a copy of array
_pages=_pages.toList();
});
},
),
),
// This helps Navigator to distigush between different pages
),key: ValueKey("home"));
}
这个页面的构建方式与_buildMain 相同,但它不是添加一个新的页面,而是删除一个并触发重建。
// This function perform same task as _buildMain
Page _buildSecondPage(){
return MaterialPage(child: Scaffold(
body: Center(
child: ElevatedButton(
child: Text("back"),
onPressed: (){
// This will take back to main
// remove the last page
_pages.removeLast();
// call setState to trigger a rebuild
setState(() {
// creating a copy of list
_pages=_pages.toList();
});
},
),
),
),key: ValueKey("second"));
}
除了使用_pages 列表作为状态外,你还可以使用任何其他状态来执行导航。下面是另一个例子。
class _App extends State {
String _selected="main";
Page _buildMain(){
return MaterialPage(child: Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: Text("click"),
onPressed: (){
setState(() {
// add a new page
_selected="second";
});
},
),
),
),key: ValueKey("home"));
}
Page _buildSecondPage(){
return MaterialPage(child: Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: Text("back"),
onPressed: (){
setState(() {
// change back state to main
_selected="main";
});
},
),
),
),key: ValueKey("second"));
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
onPopPage: (route,result){
if(route.didPop(result)){
_selected="main";
return true;
}
return false;
},
pages: [
_buildMain(),
// only show select if state has second selected
if (_selected=="second") _buildSecondPage()
],
),
);
}
}
使用RouterDelegate
RouterDelegate 是一个由Router 使用的核心部件。它对引擎的路由推送和路由弹出的意图做出响应。新的导航允许创建RouterDelegate ,以便更好地控制导航。
一个RouterDelegate 是通过扩展RouterDelegate<AppRouteState> 类与PopNavigatorRouterDelegateMixin,ChangeNotifier 混合来创建的。
_selected 追踪当前的路线。这与前面例子中使用的状态类似。
class AppRouter extends RouterDelegate<AppRouteState> with PopNavigatorRouterDelegateMixin,ChangeNotifier {
String _selected="main";
这被路由器用来获取路由器的最新状态并改变地址栏中的URL。
// get correct state of router
@override
AppRouteState get currentConfiguration => AppRouteState(_selected);
导航键是用来支持旧的导航。
// This for older navigation support.
final _navigation= GlobalKey<NavigatorState>();
@override
GlobalKey<NavigatorState> get navigatorKey => _navigation;
notifyListeners 被用来代替setState 来触发重建。_selected 被改变来改变路由。
Page _buildMain(){
return MaterialPage(child: Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: Text("click"),
onPressed: (){
_selected="second";
// notify route changes
notifyListeners();
},
),
),
),key: ValueKey("home"));
}
这类似于_buildMain 。
Page _buildSecondPage(){
return MaterialPage(child: Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
child: Text("back"),
onPressed: (){
_selected="main";
// notify route changes
notifyListeners();
},
),
),
),key: ValueKey("second"));
}
build 函数返回导航器部件,该部件用于布置其他页面。这个函数类似于前面函数中的build 。代替setState ,notifyListeners ,用于触发重建。
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Navigator(
key: _navigation,
onPopPage: (route,result){
if(!route.didPop(result)){
return false;
}
_selected="main";
// notify route changes
notifyListeners();
return true;
},
pages: [
_buildMain(),
// if Route is second show SecondPage
if (_selected=="second") _buildSecondPage()
],
),
);
}
这个函数使用路由器传递的信息来改变路由。当引擎传递路由推送或弹出的意图时,这个函数被调用来改变路由。这里传递的信息是由一个不同的类来解析的,我们将在后面讨论。
@override
Future<void> setNewRoutePath(configuration) async {
// update page based on
_selected=configuration.selected;
}
}
RouteInformationParser
setNewRoutePath 从路由器接收配置。这个配置是由RouteInformationParser 解析的。
对于操作系统、引擎等传递的解析状态,应该有一个类来扩展RouteInformationParser 。restoreRouteInformation 从currentConfiguration 中获取返回值并将其转换为RouteInformation 。
parseRouteInformation 返回路由器状态,并将其传递给setNewRoutePath 。
class AppRouteInformationParser extends RouteInformationParser<AppRouteState>{
// This converts route state to route information.
@override
RouteInformation restoreRouteInformation(configuration) {
if(configuration.selected=="main") {
return RouteInformation(location:"/main");
} else {
return RouteInformation(location: "/second");
}
}
// This converts route info to router state
@override
Future<AppRouteState> parseRouteInformation(RouteInformation routeInformation)async{
var url=Uri.parse(routeInformation.location);
print(url.path);
if (url.path == "/") return AppRouteState("main");
return AppRouteState(url.path.replaceAll("/", ""));
}
}
把它放在一起
MaterialApp 现在有一个新命名的构造函数,它实现了一个以Delegate 和InformationParser 为参数的路由器。
class _App extends State {
@override
Widget build(BuildContext context) {
return MaterialApp.router(routeInformationParser: AppRouteInformationParser(), routerDelegate: AppRouter());
}
}
总结
在这个Flutter导航教程中,我们向您介绍了如何根据Flutter 1.0中使用的命令式方法和Flutter 2.0中引入的新的声明式导航在Flutter应用中实现导航。
根据您的Flutter项目的性质,这两种类型的导航都可能是合适的,但都不是一蹴而就的。您应该始终选择最适合您需求的方法,即使这意味着采用两者的组合。
要开始使用Flutter中的导航,我推荐你查看Fluro和Voyager包。
The postUnderstanding Flutter navigation and routingappeared first onLogRocket Blog.