You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter DataTable实现左滑行显示删除按钮点击删除功能

DataTable左滑行显示删除按钮实现方案

问题说明

项目中使用Flutter DataTable组件时,需要实现交互:向左滑动表格某一行时,该行右侧呼出删除图标,点击图标即可删除对应行,效果参考如下:
左滑删除效果参考图
此前尝试在onSelectChanged回调中添加SwipeActionCell()组件实现功能,但运行未达预期,现有实现代码如下:

Expanded(
  child: SizedBox(
    child: SingleChildScrollView(
      child: DataTable(
        showCheckboxColumn: false,
        columnSpacing: unitWidthValue * 0.021,
        columns: [
          DataColumn(
            label: Center(
              child: Text('编号',
                style: TextStyle(
                  fontSize: unitWidthValue * tableHeaderTextSizePercent,
                  fontFamily: " SF Pro Display",
                  fontWeight: FontWeight.w300
                ),
              ),
            )
          ),
          DataColumn(
            label: Center(
              child: Text('名称',
                style: TextStyle(
                  fontSize: unitWidthValue * tableHeaderTextSizePercent,
                  fontFamily: " SF Pro Display",
                  fontWeight: FontWeight.w300
                ),
              ),
            )
          ),
          DataColumn(
            label: Center(
              child: Text('日期',
                style: TextStyle(
                  fontSize: unitWidthValue * tableHeaderTextSizePercent,
                  fontFamily: " SF Pro Display",
                  fontWeight: FontWeight.w300
                ),
              ),
            )
          ),
          const DataColumn(label: Text('')),
        ],
        rows: widget.persons
          .map((person) => DataRow(
            cells: <DataCell>[
              DataCell(Text(person.number)),
              DataCell(SizedBox(
                width: unitWidthValue * 0.35,
                child: Text(person.sampleNumber,
                  style: TextStyle(
                    fontSize: unitWidthValue * 0.035,
                    fontFamily: "SF Pro Display"
                  ),
                ),
              )),
              DataCell(Text(person.name,
                style: TextStyle(
                  fontSize: unitWidthValue * 0.035,
                  fontFamily: "SF Pro Display"
                )
              )),
              DataCell(Padding(
                padding: EdgeInsets.only(top: unitHeightValue * 0.02),
                child: Column(
                  children: [
                    if (person.status == personStatus.recorded || person.status == personStatus.associate)
                      const Icon(Icons.save, color: Color(0xFF0061B0),)
                    else if (person.status == personStatus.transmitted)
                      const Icon(Icons.check_circle, color: Color(0xFF34C759),)
                    else
                      const Icon(Icons.warning, color: Color(0xFFFF9500),)
                  ],
                ),
              ))
            ],
            onSelectChanged: (bool? isDispalyData) {
              setState(() {
                Navigator.of(context).push(
                  PageRouteBuilder(
                    pageBuilder: (context, animation1, animation2) => DisplayData(person, isDisplayData: true),
                    transitionDuration: Duration.zero,
                    reverseTransitionDuration: Duration.zero,
                  ),
                );
              });
            },
          ))
          .toList()
      ),
    ),
  ))
],
),
)

问题根因

onSelectChanged是DataRow的点击事件回调,仅用于处理行被点击后的业务逻辑,这个位置无法插入、渲染UI组件,写在这里的SwipeActionCell根本不会被挂载到视图树,自然不会生效。

实现方案

核心思路:将滑动交互组件放在DataCell的视图层级中,把整行内容作为滑动组件的子节点,统一承载滑动删除、点击跳转的手势逻辑,不要把UI组件写到事件回调里。

方案1:使用flutter_swipe_action_cell(和参考图效果完全一致)

该组件支持左滑后停留在显示操作按钮的状态,和给出的参考效果匹配度最高。

  • 第一步:添加依赖,在项目pubspec.yaml中写入依赖后执行flutter pub get安装:
dependencies:
  flutter_swipe_action_cell: ^3.1.3
  • 第二步:改造DataRow的单元格逻辑,将整行内容整合到第一个DataCell中,外层包裹SwipeActionCell,剩余3个单元格设置为空白占位即可,核心改造代码如下:
// 先抽离状态图标构建逻辑,减少代码嵌套
Widget _buildStatusIcon(personStatus status) {
  if (status == personStatus.recorded || status == personStatus.associate) {
    return const Icon(Icons.save, color: Color(0xFF0061B0));
  } else if (status == personStatus.transmitted) {
    return const Icon(Icons.check_circle, color: Color(0xFF34C759));
  } else {
    return const Icon(Icons.warning, color: Color(0xFFFF9500));
  }
}

// DataTable的rows部分改造
rows: widget.persons
  .map((person) => DataRow(
    // 保留原有点击行跳转逻辑
    onSelectChanged: (bool? isDisplayData) {
      setState(() {
        Navigator.of(context).push(
          PageRouteBuilder(
            pageBuilder: (context, animation1, animation2) => DisplayData(person, isDisplayData: true),
            transitionDuration: Duration.zero,
            reverseTransitionDuration: Duration.zero,
          ),
        );
      });
    },
    cells: <DataCell>[
      DataCell(
        SizedBox(
          // 宽度设置为表格行可用总宽度,保证滑动区域覆盖整行
          width: MediaQuery.of(context).size.width - 32, // 可根据自己的布局padding调整
          child: SwipeActionCell(
            key: ObjectKey(person), // 每行设置唯一key,避免列表状态错乱
            // 左侧滑动动作如果不需要可以不写,只保留右侧trailingActions
            trailingActions: <SwipeAction>[
              SwipeAction(
                title: "删除",
                style: const TextStyle(color: Colors.white, fontSize: 16),
                color: Colors.red,
                icon: const Icon(Icons.delete, color: Colors.white),
                onTap: (CompletionHandler handler) async {
                  // 执行删除逻辑
                  setState(() {
                    widget.persons.remove(person);
                  });
                  // 收回滑动操作区域
                  handler(true);
                },
              ),
            ],
            // 整行原有内容按列横向排列,宽度和表头对齐即可
            child: Row(
              children: [
                // 第一列:编号
                SizedBox(
                  width: unitWidthValue * 0.18,
                  child: Text(person.number,
                    style: TextStyle(
                      fontSize: unitWidthValue * 0.035,
                      fontFamily: "SF Pro Display"
                    ),
                  ),
                ),
                // 第二列:样本编号
                SizedBox(
                  width: unitWidthValue * 0.35,
                  child: Text(person.sampleNumber,
                    style: TextStyle(
                      fontSize: unitWidthValue * 0.035,
                      fontFamily: "SF Pro Display"
                    ),
                  ),
                ),
                // 第三列:姓名
                SizedBox(
                  width: unitWidthValue * 0.2,
                  child: Text(person.name,
                    style: TextStyle(
                      fontSize: unitWidthValue * 0.035,
                      fontFamily: "SF Pro Display"
                    ),
                  ),
                ),
                // 第四列:状态图标
                Padding(
                  padding: EdgeInsets.only(top: unitHeightValue * 0.02),
                  child: _buildStatusIcon(person.status),
                )
              ],
            ),
          ),
        ),
      ),
      // 剩余三个单元格用空白组件占位
      const DataCell(SizedBox.shrink()),
      const DataCell(SizedBox.shrink()),
      const DataCell(SizedBox.shrink()),
    ],
  ))
  .toList()
  • 手势冲突处理:如果出现滑动和点击跳转冲突,可以给SwipeActionCell设置opensOnScroll: true属性,只有横向滑动距离超过阈值时才触发滑动动作,否则正常响应点击跳转。

方案2:无第三方依赖,用官方Dismissible实现

如果不想引入第三方库,可以用Flutter自带的Dismissible组件实现滑动删除,缺点是交互为滑动超过阈值直接触发删除,无法停留在显示删除按钮的状态。
只需要把方案1里的SwipeActionCell替换为Dismissible即可:

Dismissible(
  key: ObjectKey(person),
  direction: DismissDirection.endToStart, // 仅允许从右向左滑动
  // 滑动时露出的红色删除背景
  background: Container(
    color: Colors.red,
    alignment: Alignment.centerRight,
    padding: const EdgeInsets.only(right: 24),
    child: const Icon(Icons.delete, color: Colors.white),
  ),
  // 滑动完成后触发删除
  onDismissed: (direction) {
    setState(() {
      widget.persons.remove(person);
    });
  },
  child: // 原有整行内容Row,和方案1一致
)

注意事项

  • 每个滑动行必须设置唯一key,直接用对应数据对象作为ObjectKey的值即可,避免列表更新时状态错乱
  • 需要手动调整Row内每列的宽度,和表头列宽、列间距匹配,保证内容对齐
  • 如果DataTable设置了横向滚动,需要给包裹滑动组件的SizedBox设置足够的宽度,避免内容溢出

内容的提问来源于stack exchange,提问作者compte profe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:27:34