Flutter 路由

358 阅读6分钟

简介

路由负责实现应用程序页面之间的跳转。路由分为基本路由、命名路由和嵌模式三种。

1、基本路由

1.1、基础用法

  1. 调用Navigator.push()方法。
  2. 使用MaterialPageRoute创建路由。

说明:这是一种模态路由,可以适应平台自适应的过渡效果来切换页面。默认情况下当一个模态路由被另一个替换时,上一个路由将保存在内存中,如果详释放所有的资源,可以将maintainState设置为false

  • 示例
// 跳转前页面的某个Widget点击方法中
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => PersonSettingPage()),
  );
},

在跳转后的页面,Scaffold组件会自动在AppBar上添加一个返回按钮,单击该按钮就会调用Navigator.pop方法回到跳转前页面。在自己添加的组件中调用该方法也可以回到跳转前页面。

  • 示例
onPressed: () {
  Navigator.pop(context);
},

1.2、PageRoute介绍

PageRoute是一个抽象类,它定义了页面导航行为和动画。Flutter 提供了几个常用子类:

  1. MaterialPageRoute:Material Design 风格的默认页面切换(Android 风格)。
  2. CupertinoPageRoute:Cupertino 风格的页面切换(iOS 风格)。
  3. PageRouteBuilder:自定义路由构建器

上面的示例里面用到的就是MaterialPageRoute子类

1.3、MaterialPageRoute属性介绍

MaterialPageRoute 继承自 PageRoute → ModalRoute → TransitionRoute → OverlayRoute → Route

  • builder必选,builder是一个WidgetBuilder类型的函数,作用是构建路由页面的具体内用,返回值是Widget,即新的页面实例。
  • setting:包含路由的配置信息,如路由名称(name)、传递的参数(arguments)。
  • maintainState:默认为true,默认情况下,当新页面入栈时,原来的页面会被保存在内存中,如果想在页面没有用的时候释放其所占用的资源,只需将maintainState设置为false
  • fullscreenDialog:默认false,是否以一个全屏的模态对话框展示,在iOS中,若为true,则新页面会从屏幕底部向上划入。
  • opaque:页面是否完全不透明,opaqueModalRoute类中固定为true,这是因为MaterialPageRoute 的设计目标是遵循 Material Design 规范,要求页面不透明。若想使页面可透明,可以使用其他路由类型,如PageRouteBuilder
  • barrierColor:全屏对话框背景遮罩颜色。这是一个只读属性,若想修改可以自定义类继承MaterialPageRoute,在自定义类中重写barrierColor的get方法。
  • barrierDismissible:是否允许点击遮罩关闭对话框。

1.4、Navigator

Navigator是一个路由管理的组件,负责页面的跳转和层级管理。通过栈管理活动路由集合。通常屏幕当前实现的页面就是栈顶的页面。

方法

  • push:跳转到新页面,将页面压入栈顶部。
  • pushNamed:通过预定义的命名路由跳转。
  • pushReplacement:用新的页面替换当前页面,当前页面被销毁。
  • pushReplacementNamed:通过命名路由替换当前路由。
  • pushAndRemoveUntil:跳转到新路由,并清除堆栈中指定条件之前的所有路由。
  • restorablePushNamed:支持状态恢复的命名路由跳转。
  • pop:关闭当前页面,返回上一级路由,并可选传递返回值。
  • popUntil:连续返回,直到满足指定条件。
  • removeRoute:从栈中移除指定路由。
  • replace:用新路由替换堆栈中的旧路由。
  • canPop:检查当前是否可以执行返回操作。
  • maybePop:尝试返回,如果无法返回则不执行。

2、命名路由

在 Flutter 中,命名路由(Named Routes) 是一种通过预定义路由名称来管理页面跳转的方式,可以简化导航逻辑并提高代码可维护性。

2.1、路由配置

在 MaterialApp 或 CupertinoApp 中定义路由表(routes):

MaterialApp(
  // 初始路由(可选,与 home 二选一)
  initialRoute: '/',
  // 定义路由表
  routes: {
    '/': (context) => HomePage(),
    '/details': (context) => DetailsPage(),
    '/profile': (context) => ProfilePage(),
  },
);

2.2、命名路由跳转

Navigator.pushNamed(context, "/newPage");

3、路由传值

在进行页面跳转时,通常还需要将一些数据传递给新页面,或者将数据返回给旧页面,这时就可以使用路由传值。

3.1、构造函数传值

顾名思义,在路由跳转的时候通过新页面的构造函数的参数传值给新页面。

3.2、路由setting参数传值

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) {
      return LoginSuccessPage();
    },
    settings: RouteSettings(arguments: ["数据1", "数据2", "数据3"]),
  ),
);

// 在目标页面获取传递的数据
final List arguments = ModalRoute.of(context)?.settings.arguments as List;

3.3、命名路由传值

Navigator.pushNamed(
  context,
  '/details',
  arguments: ["数据1", "数据2", "数据3"],
);

// 在目标页面获取传递的数据
final List arguments = ModalRoute.of(context)?.settings.arguments as List;

3.4、反向传值

pop函数的定义如下:

void pop<T>(BuildContext context, [T? result])

可以利用pop方法的可选参数result返回数据。

Navigator.pop(context, "返回的数据");

接收返回的数据

void onTap() async {
  var result = await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) {
        return LoginSuccessPage();
      },
      settings: RouteSettings(arguments: list),
    ),
  );

  if (result != null) {
    print("result = $result");
  }
}

4、路由拦截

通过在onGenerateRoute()方法中实现路由拦截,该方法的入参数据类型为RouteSettings,该类有两个变量,一个是Stirng?类型name,一个是Object?类型的argumentsname是定义的路由的名称,arguments则是想要传递的参数。

Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      onGenerateRoute: (settings) {
        // 拦截逻辑:若用户未登录且目标页面需要权限,跳转到登录页
        if (settings.name == '/profile' && !isLoggedIn) {
          return MaterialPageRoute(builder: (_) => LoginPage());
        }
        
        // 正常路由配置
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (_) => HomePage());
          case '/profile':
            return MaterialPageRoute(builder: (_) => ProfilePage());
          case '/login':
            return MaterialPageRoute(builder: (_) => LoginPage());
          default:
            return MaterialPageRoute(builder: (_) => NotFoundPage());
        }
      },
    );
  }

上面说的是跳转的拦截,那返回的拦截即怎么实现呢?Flutter提供了PopScope组件,该组件可用于拦截返回按钮和安卓物理返回按钮的返回操作。通过它,我们可以实现在返回前执行特定的操作(如弹窗提醒,数据保存等)。

使用示例

class UsePopScopePage extends StatelessWidget {
  const UsePopScopePage({super.key});

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, value) async {
        if (didPop) return; // 已关闭则不再处理

        final bool makeSure = await showDialog(
          context: context,
          builder:
              (context) => AlertDialog(
                title: Text("您确定要退出吗?"),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.pop(context, true),
                    child: Text("确定"),
                  ),
                  TextButton(
                    onPressed: () => Navigator.pop(context, false),
                    child: Text("取消"),
                  ),
                ],
              ),
        );
        
        /* showDialog返回值为true,且context有效情况下调用pop继续返回,
           会再次进入此方法,但此时didPop为true,直接返回。*/
        if (makeSure && context.mounted) {
          Navigator.pop(context);
        }
      },
      child: Scaffold(
        appBar: AppBar(title: Text("返回拦截")),
        body: Center(child: Text("这里是返回拦截演示")),
      ),
    );
  }
}

参数介绍

canPop:实际开发中可能会根据全局状态或者局部状态来赋值,需注意,若想保留iOS、Android侧滑返回功能,需设为true。若设为true,则onPopInvokedWithResult方法中的didPoptrue。若为false,则didPopfalse,此时可以添加额外操作以决定是否继续返回。

onPopInvokedWithResult:此方法类型定义为:void Function(bool didPop, T? result)

  • didPop:bool类型,表示路由是否已经弹出。
  • result:可空泛型参数,表示导航返回时开发者想要回传的数据。