傳遞資料到新頁面

在開發的過程中,我們經常需要在跳轉到新頁面的時候,能同時傳遞一些資料。比如,傳遞使用者點選的元素訊息。

還記得麼,全屏的介面也只是 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),
      ),
    );
  }
}