一个App是由很多page(页面)组成的,咱们需求点击跳转到不同的页面,而不是仅仅单纯经过BottomNavigationBarItem
点击设置IndexedStack
的特点进行切换页面。通常咱们会经过路由来统一的办理跳转。
什么是路由
存在一个路由映射表,经过仅有的标识找到要跳转的页面。在Flutter中,路由办理主要有两个类:Route
和Navigator
Route
一个页面要想被路由统一办理,有必要包装为一个 Route。MaterialPageRoute
是PageRoute
的子类,表明一个模态路由页面,还界说了路由构建及切换时过渡动画的相关接口及特点。MaterialPageRoute 是 Material
组件库供给的组件,它能够针对不同渠道,实现与渠道页面切换动画风格一致的路由切换动画。
- 在 iOS 渠道,翻开一个页面会从右边滑动到屏幕左面,回来时会从左面到右边消失
- 在 Android 渠道,翻开一个页面会从屏幕底部滑动到屏幕的顶部,封闭页面时从顶部滑动到底部消失
承继联系是这样的
MaterialPageRoute->PageRoute->ModalRoute->TransitionRoute->OverlayRoute->Route
Navigator
Navigator:办理一切的 Route 的 Widget,经过栈来进行办理的,通常当时屏幕显现的页面便是栈顶的路由。
组件路由
路由跳转,传入一个路由对象route
。该方法是把新的路由添加到Navigator办理的路由对象的栈顶
Navigator.of(context).push(route)
回来上个页面,这儿回来时能够带参数的[ T? result ]
,退出则是从栈顶把路由对象移出
Navigator.of(context).pop()
示例代码
child: ElevatedButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
return PageDetail();
}));
},
child: Text('进入概况页面'),
),
child: ElevatedButton(
onPressed: (){
Navigator.of(context).pop();
},
child: Text('回来'),
),
导航回来拦截
为了防止用户误触回来按钮而导致 App 退出,在很多 App 中都拦截了用户点击回来键的按钮,然后进行一些防误触判别,比方当用户在某一个时间段内点击两次时,才会认为用户是要退出(而非误触)。Flutter 中能够经过WillPopScope
来实现回来按钮拦截,咱们看看 WillPopScope 的默认构造函数:
const WillPopScope({
...
required WillPopCallback onWillPop,
required Widget child
})
onWillPop
是一个回调函数,当用户点击回来按钮时被调用(包含导航回来按钮及 Android 物理回来按钮)。该回调需求回来一个Future
对象,假如回来的 Future 最终值为false
时,则当时路由不出栈(不会回来);最终值为true
时,当时路由出栈退出。咱们需求供给这个回调来决定是否退出。
@override
Widget build(BuildContext context) {
WillPopScope(
onWillPop: onWillPop,
child: Scaffold(
)
)
onWillPop:() {
print('即将退出本界面');
//return Future.value(false);//阻挠退出
return Future.value(true); //退出
}
}
命名路由
基本路由运用仍是比较简单的,可是对于页面比较多的场景就不太习惯用了,每次跳转新页面就要创立新路由,这样便是比较紊乱了。而命名路由是给每个页面起了一个别号,经过这个别号就能够找到翻开这个页面,这样办理起来就比较明晰便利了。
要想经过别号来实现页面切换,就需求用到 MaterialApp 供给的一个页面名称映射规则,也便是路由表
。这个路由表是一个Map结构,key是页面的别号
,value便是对应页面
。
Navigator 运用的4个关键特点
- initialRoute: 初始路由的,也便是进入APP,默认页面
- onGenerateRoute: 路由拦截器,当需求对某个路由做特别处理时能够运用这个
- onUnknownRoute: 找不到页面,默认创立一个错误页面
- routes:也就在履行路由跳转的时分,会到路由调集里边的子路由进行匹配,假如匹配 到那么就调整到指定页面
示例代码
封装一个 routes.dart
import 'package:flutter/material.dart';
import '../pages/tabs.dart';
import '../pages/search.dart';
import '../pages/product.dart';
//装备路由,界说Map类型的routes,Key为String类型,Value为Function类型
final Map<String,Function> routes={
'/':(context)=>Tabs(),
'/product':(context)=>ProductPage(),
'/search':(context,{arguments})=>SearchPage(arguments:arguments),
};
//固定写法
var onGenerateRoute=(RouteSettings settings) {
//String? 表明name为可空类型
final String? name = settings.name;
//Function? 表明pageContentBuilder为可空类型
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
}else{
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context));
return route;
}
}
};
在main.dart里边装备
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// home:Tabs(),
initialRoute: '/', //初始化的时分加载的路由
onGenerateRoute: onGenerateRoute
);
}
}
跳转代码
ElevatedButton(
child: Text("跳转到产品页面"),
onPressed: () {
Navigator.pushNamed(context, '/product');
}
),
路由钩子
MaterialApp有一个onGenerateRoute
特点,当调用Navigator.pushNamed(…)翻开命名路由时,假如指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;假如路由表中没有注册,才会调用onGenerateRoute来生成路由。然后能够在路由中经过参数RouteSettings
来判别是否需求特别处理,比方能够把需求权限判别跳转的路由放到这儿翻开。
onGenerateRoute: (settings) {
if (settings.name == "login") {
return MaterialPageRoute(
builder: (context) {
return LoginPage(settings.arguments);
}
);
}
return null;
},
Navigator 怎么传参和回传值
- 注册好之后就能够经过
Navigator.pushNamed()
翻开页面了
Navigator.pushNamed(context, "basic");
//带参数的跳转
Navigator.pushNamed(context, "basic", arguments: '要传的参数');
- 获取传递的参数
Widget build(BuildContext context) {
// 1.获取数据
final args= ModalRoute.of(context)!.settings.arguments;
}
- 回来页面是经过
Navigator.pop
Navigator.pop(context);
Navigator.pop(context, "回来值");
- 接纳回来的数据,需求修正下跳转方法
Navigator.pushNamed(context, 'basic').then((value) => print('接纳到的数据${value}'));
- 回来到
指定的页面
是经过Navigator.popUntil
Navigator.popUntil(context, ModalRoute.withName('路由'));
在平常运用过程中,是能够把Routes
和onGenerateRoute
单独提取出来成为一个类,这样后期保护就比较明晰和修正便利。
命名路由的最重要作用,便是建立了字符串标识符与各个页面之间的映射联系,使得各个页面之间彻底解耦,运用内页面的切换只需求经过一个字符串标识符就能够搞定,为后期模块化打好基础。
Navigator的各种跳转方法详解
-
pushAndRemoveUntil
:跳转到新的页面,并且删去新页面和底部页面之间一切页面 -
pushReplacement
:新的页面替换当时页面,只需求创立新的页面,当时页面毁掉 -
pushReplacementNamed
:新的页面替换当时页面,仅仅路由的传递,命名路由方法,当时页面毁掉 -
popUntil
:回来到指定页面,其他页面毁掉 -
popAndPushNamed
:退出当时页面并从弹出新的页面 -
canPop
:判别当时页面能否被弹出栈,栈内只要一个页面时为false,别的时分为true -
maybePop
:判别根据便是看当时路由是否处在栈中“最底部”的位置,假如不是就退出
pushReplacementNamed和popAndPushNamed
有 A、B、C 三个页面,A页面经过 pushNamed 跳转到 B,B 经过 pushReplacementNamed
跳转到 C,点击 C 页面按钮履行 pop。点击 C 页面按钮直接回来到了 A 页面,而不是 B 页面,由于 B 页面运用 pushReplacementNamed 跳转,路由仓库改变:
假如 B 页面跳转到 C 页面,运用 popAndPushNamed
,popAndPushNamed 路由仓库和 pushReplacementNamed 是一样,仅有的区别便是 popAndPushNamed 有 B 页面退出动画。
pushNamedAndRemoveUntil
有如下场景,运用程序进入主页,点击登录进入登录页面,然后进入注册页面或许忘掉密码页面…,登录成功后进入其他页面,此刻不期望回来到登录相关页面,此场景能够运用 pushNamedAndRemoveUntil。
有A、B、C、D 四个页面,A 经过push进入 B 页面,B 经过push进入 C 页面,C 经过 pushNamedAndRemoveUntil
进入 D 页面同时删去路由仓库中直到 /B 的路由,C 页面代码:
RaisedButton(
child: Text('C 页面'),
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
},
),
D 页面按钮履行 pop,从 C 到 D 仓库的改变
Navigator.of(context).pushNamedAndRemoveUntil('/D', ModalRoute.withName('/B'));
表明跳转到 D 页面,同时删去D 到 B 直接一切的路由,假如删去一切路由,只保存 D
Navigator.of(context).pushNamedAndRemoveUntil('/D', (Route route)=>false);
路由仓库改变:
popUntil
有A、B、C、D 四个页面,D 页面经过 popUntil 一向回来到 A 页面,D 页面代码:
RaisedButton(
child: Text('D 页面'),
onPressed: () {
Navigator.of(context).popUntil(ModalRoute.withName('/A'));
},
)
maybePop 和 canPop
在 A 页面时路由仓库中只要 A,调用 pop 后,路由仓库改变
此刻路由仓库为空,没有可显现的页面,运用程序将会退出或许黑屏,好的用户体验不应如此,此刻能够运用 maybePop,maybePop 只在路由仓库有可弹出路由时才会弹出路由
上面的事例在 A 页面履行maybePop:
RaisedButton(
child: Text('A 页面'),
onPressed: () {
Navigator.of(context).maybePop();
},
)
点击后不会出现弹出路由,由于当时路由仓库中只要 A,在 B页面履行maybePop,将会回来到 A 页面。
也能够经过 canPop 判别当时是否能够 pop:
RaisedButton(
child: Text('B 页面'),
onPressed: () {
if(Navigator.of(context).canPop()){
Navigator.of(context).pop();
}
},
)
三方插件 fluro
fluro
作为一款优秀的 Flutter 企业级路由框架,fluro的运用比官方供给的路由框架要复杂一些,可是却非常合适中大型项目。由于它具有层次分明、条理化、便利扩展和便于全体办理路由等优点。
fluro 的github链接:github.com/lukepighett…
参阅文章:这篇文章对于路由仓库解说的很详细:/post/687286…
监听路由仓库的改变:/post/687322…