實現「滑動清除」效果

『滑動清除』在許多移動應用中都很常見。比如,我們在寫一個郵件應用,我們會想讓使用者能夠滑動刪除清單中的郵件訊息。使用者操作時,我們可能需要把這封郵件從收件箱移動到垃圾箱。

Flutter 提供了 Dismissible Widget 來輕鬆地實現這個需求。我們一起看一下如下的步驟:

步驟

  1. 建立專案清單

  2. 把每一項打包成一個 Dismissible Widget

  3. 提供『滯留』提示

1. 建立專案清單

首先,我們建立一個清單,清單項是能夠滑動清除的。至於如何建立清單的更多細節,請參考 長清單的處理 文件。

建立一個資料來源

在我們的例子中,我們需要 20 個樣本項來實現清單。為簡單起見,我們會生成一個字串清單。

final items = List<String>.generate(20, (i) => 'Item ${i + 1}');

將資料來源轉換成一個 List

首先,我們簡單地在螢幕上展示清單中的每一項,使用者現在還無法滑動清除它們。

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

2. 把每一項打包一個 Dismissible Widget

在這個步驟中,使用者可以透過使用 Dismissible 來刪除清單中的某項。

在使用者將某一項滑出螢幕後,我們需要將那一項從清單中刪除並顯示一個 Snackbar。在真實的應用中,你可能需要執行更復雜的邏輯,比如從網頁服務或資料庫中刪除此項。

我們可以透過更新 itemBuilder() 函式來回傳一個 Dismissible widget:

itemBuilder: (context, index) {
  final item = items[index];
  return Dismissible(
    // Each Dismissible must contain a Key. Keys allow Flutter to
    // uniquely identify widgets.
    key: Key(item),
    // Provide a function that tells the app
    // what to do after an item has been swiped away.
    onDismissed: (direction) {
      // Remove the item from the data source.
      setState(() {
        items.removeAt(index);
      });

      // Then show a snackbar.
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('$item dismissed')));
    },
    child: ListTile(
      title: Text(item),
    ),
  );
},

3. 提供『滯留』提示

顧名思義,我們的應用允許使用者將清單項滑出清單,但是應用可能沒有向用戶給出視覺提示,告訴他們操作時發生了什麼。要給出提示,表明我們正在刪除清單項,就需要在他們將清單項滑出螢幕的時候,展示一個『滯留』提示。這個例子中,我們使用了一個紅色背景。

出於這個目的,我們為 Dismissible 設定了一個 background 引數。

lib/{step2.dart (Dismissible) → main.dart (Dismissible)}
@@ -16,6 +16,8 @@
16
16
  ScaffoldMessenger.of(context)
17
17
  .showSnackBar(SnackBar(content: Text('$item dismissed')));
18
18
  },
19
+ // Show a red background as the item is swiped away.
20
+ background: Container(color: Colors.red),
19
21
  child: ListTile(
20
22
  title: Text(item),
21
23
  ),

互動式範例

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// MyApp is a StatefulWidget. This allows updating the state of the
// widget when an item is removed.
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  MyAppState createState() {
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  final items = List<String>.generate(20, (i) => 'Item ${i + 1}');

  @override
  Widget build(BuildContext context) {
    const title = 'Dismissing Items';

    return MaterialApp(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text(title),
        ),
        body: ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            final item = items[index];
            return Dismissible(
              // Each Dismissible must contain a Key. Keys allow Flutter to
              // uniquely identify widgets.
              key: Key(item),
              // Provide a function that tells the app
              // what to do after an item has been swiped away.
              onDismissed: (direction) {
                // Remove the item from the data source.
                setState(() {
                  items.removeAt(index);
                });

                // Then show a snackbar.
                ScaffoldMessenger.of(context)
                    .showSnackBar(SnackBar(content: Text('$item dismissed')));
              },
              // Show a red background as the item is swiped away.
              background: Container(color: Colors.red),
              child: ListTile(
                title: Text(item),
              ),
            );
          },
        ),
      ),
    );
  }
}