实现「滑动清除」效果

“滑动清除”在许多移动应用中都很常见。比如,我们在写一个邮件应用,我们会想让用户能够滑动删除列表中的邮件消息。用户操作时,我们可能需要把这封邮件从收件箱移动到垃圾箱。

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.

// MyApp是一个StatefulWidget。这样,我们就能够在列表项被移除的时候,更新Widget的状态。

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(
        primarySwatch: Colors.blue,
      ),
      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.
              // 每个Dismissible实例都必须包含一个Key。Key让Flutter能够对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.
              // 列表项被滑出时,显示一个红色背景(Show a red background as the item is swiped away)
              background: Container(color: Colors.red),
              child: ListTile(
                title: Text(item),
              ),
            );
          },
        ),
      ),
    );
  }
}