likes
comments
collection
share

Flutter的路由与导航,以及页面之间如何共享数据

作者站长头像
站长
· 阅读数 28

定义

绝大多数应用应该会由多个不同的界面组成,不同的页面显示不同类型的信息。比如一个应用可以有注册页面,可以有登录页面,可以有用户信息页面,还应该有应用主功能页面,等等。关于路由和导航的定义,我就不咬文嚼字,简单说一下我的理解,在Flutter中,页面和屏也就是用户可见的页面都是路由。在 Android 开发中,Activity 相当于“路由”,在iOS开发中,ViewController 相当于“路由”。在 Flutter 中,“路由”也是一个 widget。现实生活中的导航很容易让人理解,就是出发地如何到目的的交通方式的组合,在应用开发中可以理解为从原页面跳转到目标页面的路径选择过程。业务不复杂,页面比较少的应用,同时没有Deep linking,建议使用Navigator。

导航到一个新页面和返回

怎样从一个“路由”跳转到一个新的“路由”,也就是说页面之间的导航(跳转)会用到 Navigator。路由的下面来展示如何在两个路由间跳转,总共分三步:

  1. 创建两个路由。
  2. 用Navigator.push()跳转到第二个路由。
  3. 用 Navigator.pop()会退到第一个(上一个)路由。 Navigator的底层数据结构应该是使用的栈这个数据结构,栈的特点:先进后出,后进先出。Navigator保存的是页面堆栈数据。Navigator的push方法有两个参数,一个BuildContext,一个Route,源码如下:

Flutter的路由与导航,以及页面之间如何共享数据 Flutter源码有很好的解释说明的文案和方法调用的示例,对我们学习Dart和Flutter有很好的帮助。 pop的方法有两个参数,一个BuildContext,一个返回参数result,源码如下:

Flutter的路由与导航,以及页面之间如何共享数据

pop()方法调用中result是可选的,可以给原来的页面返回数据,同样也可以不返回数据,所以我们做返回数据处理的时候需要对数据做判空处理。Android中的Activity之间传递数据的时候,数据需要实现Parcelable接口,为什么Flutter不需要呢。因为Activity传递数据可能是不同进程之间传递数据,而Flutter是相同进程之间传递数据。

传递数据通过命名式路由

Navigator还有一种命名式的路由,代码示例:

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

命名式式路由可以处理深度连接,但是行为总是相同而且不能定制。当一个新的深度连接被平台接受以后,Flutter会创建一个新的路由,而不是用原来的路由进行处理,尽管当前路由(页面)是同一类路由。

当使用命名式路由的时候,Flutter也不支持浏览器向前按钮的点击。通过这些原因,我们不建议在绝大多数应用中使用命名式路由。

传递数据到一个新页面

push()方法方法调用过程中使用的Route经常使用MaterialPageRoute这个类。

Flutter的路由与导航,以及页面之间如何共享数据 第二个参数settings的实现,源码如下:

Flutter的路由与导航,以及页面之间如何共享数据 传递参数示例代码如下:

Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const DetailScreen(),
      // Pass the arguments as part of the RouteSettings. The
      // DetailScreen reads the arguments from these settings.
      settings: RouteSettings(
        arguments: todo,
      ),
    ),
  );

Navigator.push()给第二个页面传递数据有两种方式,需要根据自己的业务场景进行选择。

  1. 直接作为目标页面Widget构造方法中的参数。(直接赋值,页面中可以直接使用)。
  2. 作为MaterialPageRoute的settings参数的arguments,进行传递。(需要进行获取值并进行转化才可以直接使用)。需要在Widget的build方法中获取参数值,示例代码如下:
final todo = ModalRoute.of(context)!.settings.arguments as Todo;

从一个页面回传数据

从一个页面向原来的页面传递数据需要两个步骤:

  1. 从退出的页面调用Navigator.pop()方法的时候把数据传递回去,例如 Navigator.pop(context, "Return yes data")。 2.从原来页面需要接受数据并实现相应的业务逻辑,注意push是一个异步方法,调用push方法的时候需要用await关键字。 完整代码如下:

import 'package:flutter/material.dart';

class Todo {
  final String title;
  final String description;

  const Todo(this.title, this.description);
}

void main() {
  runApp(
    MaterialApp(
      title: 'Passing Data',
      home: TodosScreen(
        todos: List.generate(
          20,
          (i) => Todo(
            'Todo $i',
            'A description of what needs to be done for Todo $i',
          ),
        ),
      ),
    ),
  );
}

class TodosScreen extends StatelessWidget {
  const TodosScreen({super.key, required this.todos});

  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todos'),
      ),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            // When a user taps the ListTile, navigate to the DetailScreen.
            // Notice that you're not only creating a DetailScreen, you're
            // also passing the current todo through to it.
            onTap: () {
              _navigateAndDisplaySelection(context, todos[index]);
            },
          );
        },
      ),
    );
  }
}

Future<void> _navigateAndDisplaySelection(
    BuildContext context, Todo todo) async {
  var result = await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const DetailScreen(),
      // Pass the arguments as part of the RouteSettings. The
      // DetailScreen reads the arguments from these settings.
      settings: RouteSettings(
        arguments: todo,
      ),
    ),
  );
  // When a BuildContext is used from a StatefulWidget, the mounted property
  // must be checked after an asynchronous gap.
  if (!context.mounted) return;

  // After the Selection Screen returns a result, hide any previous snackbars
  // and show the new result.
  ScaffoldMessenger.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text('$result')));
}

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

  @override
  Widget build(BuildContext context) {
    final todo = ModalRoute.of(context)!.settings.arguments as Todo;

    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Text(todo.description),
            ElevatedButton(
                onPressed: () {
                  Navigator.pop(context, "Return yes data");
                },
                child: const Text("Return yes data")),
            ElevatedButton(
                onPressed: () {
                  Navigator.pop(context, "Return no data");
                },
                child: const Text("Return no data")),
          ],
        ),
      ),
    );
  }
}

页面之间其他传递数据的方式还有哪些呢

  1. 全局状态是最简单但可扩展性最差的方法。它涉及使用全局变量来存储数据。代码可测试性和可维护性都不是特别好。
  2. InheritedWidget允许您有效地在widget树中传播数据。
  3. Provider是Flutter团队推荐的状态管理解决方案。它可以轻松管理状态和依赖注入。 上面这个三个方案从性能,代码可维护性,可扩展性等方面依次增强,更推荐使用Provider。

总结

随着业务之间的逐渐复杂,不同页面之间的跳转会异常复杂,路由和导航相关的知识已经成为开发过程中不可获取的组成部分,希望文章对您有帮助,如果文中有问题,希望您不吝指教。

转载自:https://juejin.cn/post/7387701265796612132
评论
请登录