路由堆栈
Flutter 路由管理中有两个非常重要的概念:
- Route:路由是应用程序页面的抽象,对应 Android 中 Activity 和 iOS 中的 ViewController,由 Navigator 管理。
- Navigator:Navigator 是一个组件,管理和维护一个基于堆栈的历史记录,通过 push 和 pop 进行页面的跳转。
简单示例
class _YcHomeBodyState extends State<YcHomeBody> {
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
// 使用PageRouteBuilder来加入淡入淡出的切换效果
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) =>
const SecondPage(),
));
},
child: const Text("跳转到B页面"),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("B页面", style: TextStyle(color: Colors.white)),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("返回主页面"),
),
),
);
}
}
命名路由
使用命名路由需要在 MaterialApp 中配置路由名称
配置
MaterialApp(
title: 'Flutter Demo',
routes: <String, WidgetBuilder>{
'/A': (context) => APage(),
'/B': (context) => BPage(),
},
home: Scaffold(
body: APage(),
),
)
跳转方法
Navigator.of(context).pushNamed('/B');
// 页面替换,一般用于不希望再返回上一个页面,比如欢迎页和登录页
Navigator.of(context).pushReplacementNamed('/B');
// 用于判断路由是否可以出栈
Navigator.of(context).canPop()
// 路由出栈
Navigator.of(context).pop();
// 实现在跳转到新页面的同时,将之前的所有页面都清除掉,只保留当前页面
// 比如:应用程序进入首页,点击登录进入登录页面,然后进入注册页面或者忘记密码页面...,登录成功后进入其他页面,此时不希望返回到登录相关页面,此场景可以使用
Navigator.pushNamedAndRemoveUntil
全局路由切换动画
使用系统提供的路由切换动画
淡入淡出动画
MaterialApp(
routes: <String, WidgetBuilder>{
'/A': (context) => const YcHomePage(),
'/B': (context) => const SecondPage(),
},
// 设置页面过度主题
theme: ThemeData(
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android:FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS:FadeUpwardsPageTransitionsBuilder()
}
)
),
//指定显示哪一个页面
home: const YcHomePage(),
);
}
}
FadeUpwardsPageTransitionsBuilder
:在页面切换时实现从下往上淡入的效果。OpenUpwardsPageTransitionsBuilder
:用于构建从下往上的页面转场动画。在这种动画中,新页面从屏幕底部向上滑动,同时旧页面会向上淡出。ZoomPageTransitionsBuilder
:在页面切换时实现缩放效果。CupertinoPageTransitionsBuilder
:IOS风格的页面切换动画
路由传参
构造函数传参
此种方式无法用于命名路由的跳转方式
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return ProductDetail(productInfo: productInfo,);
}));
class ProductDetail extends StatelessWidget {
final ProductInfo productInfo;
const ProductDetail({Key key, this.productInfo}) : super(key: key);
Widget build(BuildContext context) {
return Container();
}
}
通过命名路由设置参数的方式
主页面 - > B页面
// 发送参数
Navigator.of(context).pushNamed('/B', arguments: '来自主页面');
// 接收参数
Text("B页面-${ModalRoute.of(context)?.settings.arguments}")
B页面 - > 主页面
// 发送参数
Navigator.of(context).pop('从B页面返回');
// 接收参数
ElevatedButton(
onPressed: () async {
String res = await Navigator.of(context).pushNamed('/B', arguments: '来自主页面') as String;
setState((){
title = res.toString();
});
},
child: Text(title),
),
监听应用程序路由堆栈变化
具体内容见:监听应用程序路由堆栈变化
自定义RouteObserver
,继承RouteObserver
并重写其中的方法:
class MyRouteObserver<R extends Route<dynamic>> extends RouteObserver<R> {
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
print('didPush route: $route,previousRoute:$previousRoute');
}
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
print('didPop route: $route,previousRoute:$previousRoute');
}
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
print('didReplace newRoute: $newRoute,oldRoute:$oldRoute');
}
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
print('didRemove route: $route,previousRoute:$previousRoute');
}
void didStartUserGesture(Route route, Route? previousRoute) {
super.didStartUserGesture(route, previousRoute);
print('didStartUserGesture route: $route,previousRoute:$previousRoute');
}
void didStopUserGesture() {
super.didStopUserGesture();
print('didStopUserGesture');
}
}
使用
void main() {
runApp(MyApp());
}
MyRouteObserver<PageRoute> myRouteObserver = MyRouteObserver<PageRoute>();
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
navigatorObservers: [myRouteObserver],
initialRoute: '/A',
home: APage(),
);
}
}
拦截返回事件
WillPopScope
是 Flutter 中的一个小部件,它可以用来控制 Android 和 iOS 平台上的返回按钮行为。通常情况下,当用户点击返回按钮时,应用程序会关闭当前页面并返回上一个页面。但是,有时候我们需要在用户点击返回按钮时执行一些自定义的操作,例如弹出一个确认对话框或者执行一些数据保存操作等。
WillPopScope
就是用来实现这种自定义返回按钮行为的。它可以监听用户点击返回按钮的事件,并根据需要执行一些操作。在 WillPopScope
中,我们可以通过 onWillPop
回调函数来处理返回按钮事件。如果 onWillPop
返回 true
,则表示允许关闭当前页面并返回上一个页面;如果返回 false
,则表示不允许关闭当前页面。
通常,它被放置在主页面的Scaffold
的body
属性中,以便在用户按下返回按钮时,可以在Scaffold
中处理返回操作。
Scaffold(
//导航条
appBar: AppBar(
title: const Text("路由", style: TextStyle(color: Colors.white)),
),
//页面主题内容
body: WillPopScope(
onWillPop: () async {
bool confirmExit = await showDialog(
context: context,
builder: (content) {
return AlertDialog(
title: const Text('确定要推出吗?'),
actions: [
ElevatedButton(
child: const Text('退出'),
onPressed: () => Navigator.of(context).pop(true)),
ElevatedButton(
child: const Text('取消'),
onPressed: () => Navigator.of(context).pop(false)),
],
);
});
return confirmExit;
},
child: const YcHomeBody(),
));
自定义独立路由
Navigator
是管理路由的控件,通常情况下直接使用Navigator.of(context)
的方法来跳转页面,之所以可以直接使用Navigator.of(context)
是因为在WidgetsApp
中使用了此控件,应用程序的根控件通常是MaterialApp
,MaterialApp
包含WidgetsApp,所以可以直接使用Navigator
的相关属性。
Navigator
用法非常简单,如下:
Navigator(
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'home':
builder = (context) => PageA();
break;
case 'user':
builder = (context) => PageB();
break;
}
return MaterialPageRoute(builder: builder, settings: settings);
},
)
initialRoute
表示初始化路由,onGenerateRoute
表示根据RouteSettings生成路由。
常见场景
那么在什么情况下需要使用Navigator?在需要局部页面跳转的地方使用Navigator,如下面的场景:
头条客户端举报场景
头条客户端每一个新闻下面都有一个“叉号”,点击弹出相关信息,点击其中的举报,会在当前小窗户内跳转到举报页面并不是全屏切换页面,而是仅仅在当前弹出的页面进行切换。
主页面
class _YcHomeBodyState extends State<YcHomeBody> {
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: 350,
width: 300,
child: Navigator(
initialRoute: '/',
onGenerateRoute: (RouteSettings settins) {
// 设置默认值
WidgetBuilder builder = (content) => const Center();
switch (settins.name) {
case '/':
builder = (context) => const SecondPage();
break;
}
return MaterialPageRoute(builder: builder);
},
),
),
);
}
}
SecondPage
页面
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: 100,
child: Card(
child: Column(
children: <Widget>[
_buildItem(Icons.access_alarm, '举报', '标题夸张,内容质量差 等',
showArrow: true, onPress: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return const ThirdPage();
}));
}),
],
),
),
));
}
_buildItem(IconData iconData, String title, String content,
{bool showArrow = false, required VoidCallback onPress}) {
return Row(
children: <Widget>[
Icon(iconData),
const SizedBox(
width: 20,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: const TextStyle(fontSize: 18),
),
Text(
content,
style: TextStyle(
color: Colors.black.withOpacity(.5), fontSize: 14),
)
],
),
),
!showArrow
? Container()
: IconButton(
icon: const Icon(Icons.arrow_forward_ios),
iconSize: 16,
onPressed: onPress,
),
],
);
}
}
ThirdPage
class ThirdPage extends StatelessWidget {
const ThirdPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Container(
height: 50,
width: 250,
color: Colors.grey.withOpacity(.5),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
const Text("抱歉系统升级中,暂时无法举报")
],
),
],
),
);
}
}