传递数据到新页面

在开发的过程中,我们经常需要在跳转到新页面的时候,能同时传递一些数据。比如,传递用户点击的元素信息。

还记得么,全屏的界面也只是 widget。在这个例子中,我们会创建一个待办事项列表,当某个事项被点击的时候,会跳转到新的一屏 (widget),在新的一屏显示待办事项的详细信息。

  1. 定义一个描述待办事项的数据类

  2. 显示待办事项

  3. 创建一个显示待办事项详细信息的界面

  4. 传递数据并跳转到待办事项详细信息界面

1. 定义一个描述待办事项的数据类

首先,我们需要一个简单的方式来描述待办事项。我们创建一个类叫做 Todo,包含 titledescription 两个成员变量。

class Todo {
  final String title;
  final String description;

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

2. 创建待办事项列表

第二步,我们需要显示一个待办事项列表,生成 20 条待办事项并用 ListView 显示。如果你想了解更多关于列表显示的内容,请阅读文档 基础列表

生成待办事项列表

final todos = List.generate(
  20,
  (i) => Todo(
    'Todo $i',
    'A description of what needs to be done for Todo $i',
  ),
);

ListView 显示待办事项列表

ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
    );
  },
),

到目前为止,我们生成了 20 条待办事项,并用 ListView 把它显示出来了。

3. 创建一个待办页面显示待办事件列表

为了实现这个,我们要创建一个无状态的 widget (StatelessWidget),我们叫它 TodosScreen。因为这个页面在运行时内容并不会变动,在这个 widget 的 scope 里,我们会把这个待办事项的数组设置为必须 (加入 @require 限定符)。

我们把 ListView.builder 作为 body 的参数返回给 build() 方法,这将会把列表渲染到屏幕上供你继续下一步。

class TodosScreen extends StatelessWidget {
  // Requiring the list of todos.
  const TodosScreen({super.key, required this.todos});

  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todos'),
      ),
      //passing in the ListView.builder
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
          );
        },
      ),
    );
  }
}

使用 Flutter 自带的样式,未来会变得很轻松。

4. 创建一个显示待办事项详细信息的界面

现在,我们来创建第二个全屏的界面,界面的标题是待办事项的标题,界面下面显示待办事项的描述信息。

这个界面是一个 StatelessWidget,创建的时需要传递 Todo 对象给它,它就可以使用传给他的 Todo 对象来构建 UI 。

class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});

  // Declare a field that holds the Todo.
  final Todo todo;

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}

5. 传递数据并跳转到待办事项详细信息界面

上面写完了 DetailScreen ,现在该执行界面跳转啦!我们想让用户在点击列表中的某个待办事项时跳转到 DetailScreen 界面,同时能传递点击的这条代办事项对象(Todo 对象)。

想要获取到用户在 TodosScreen 的点击事件,我们来编写 ListTile widget 的 onTap() 回调函数,继续使用 Navigator.push() 方法。

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: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DetailScreen(todo: todos[index]),
          ),
        );
      },
    );
  },
),

交互式样例

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: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DetailScreen(todo: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});

  // Declare a field that holds the Todo.
  final Todo todo;

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}

或者使用 RouteSettings 传递参数

重复前面两个步骤。

创建一个详情页以提取参数

接下来,创建一个详情页用于提取并显示来自 Todo 页面的标题和描述信息。为了访问 Todo 页面,请使用 ModalRoute.of() 方法。它将会返回带有参数的当前路由。

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: Text(todo.description),
      ),
    );
  }
}

最后,当用户点击 ListTile widget 时,使用 Navigator.push() 导航到 DetailScreen。将参数作为 RouteSettings 的一部分进行传递, DetailScreen 将会提取这些参数。

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: () {
        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: todos[index],
            ),
          ),
        );
      },
    );
  },
)

完整样例

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: () {
              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: todos[index],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

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: Text(todo.description),
      ),
    );
  }
}