Flutter:路由、拦截返回事件、自定义独立路由

路由堆栈

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

路由抽离

import 'package:flutter/material.dart';
import 'package:flutterdemo/route_pages/BottomNav.dart';
import 'package:flutterdemo/route_pages/PageOne.dart';
final routes = {
  '/': (context) => BottomNav(),
  '/pageone': (context, {arguments}) => PageOne(arguments: arguments)
};

Route onGenerateRoute (RouteSettings settings) {
  final String name = settings.name;
  final Function routeBuilder = routes[name];
  if (routeBuilder != null) {
    if (settings.arguments != null) {
      final Route route = MaterialPageRoute(
        builder: (context) => routeBuilder(context, arguments: settings.arguments)
      );
      return route;
    }else {
      final Route route = MaterialPageRoute(
          builder: (context) => routeBuilder(context)
      );
      return route;
    }
  }
}
import 'package:flutter/material.dart';
import './pages/route/Page.dart';
import 'package:flutterdemo/routes/Routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
  
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateRoute: onGenerateRoute,
      initialRoute: '/',   
    );
  }
}

全局路由切换动画

使用系统提供的路由切换动画

淡入淡出动画

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,则表示不允许关闭当前页面。

通常,它被放置在主页面的Scaffoldbody属性中,以便在用户按下返回按钮时,可以在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中使用了此控件,应用程序的根控件通常是MaterialAppMaterialApp包含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("抱歉系统升级中,暂时无法举报")
            ],
          ),
        ],
      ),
    );
  }
}

请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知的小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值