鸿蒙Flutter实战:多选批量删除模式的实现
单条删除用滑动操作就够了。但如果用户想一口气删除十几条过期的备忘录,一条一条滑得累死。批量删除需要一套"选择模式"——用户通过长按进入选择态,勾选多个条目,然后一键删除。

前言
单条删除用滑动操作就够了。但如果用户想一口气删除十几条过期的备忘录,一条一条滑得累死。批量删除需要一套"选择模式"——用户通过长按进入选择态,勾选多个条目,然后一键删除。
这和文件管理器、相册的多选逻辑是一样的:长按激活选择模式 → 点击勾选/取消 → 全选/反选 → 批量操作。本文拆解鸿蒙 Flutter 备忘录中这个功能的完整实现。
项目仓库:todo_flutter_harmony
状态设计
选择模式需要在 MemoProvider 中维护两组状态:
class MemoProvider extends ChangeNotifier {
bool _isSelectionMode = false;
final Set<int> _selectedIds = {};
bool get isSelectionMode => _isSelectionMode;
Set<int> get selectedIds => Set.unmodifiable(_selectedIds);
int get selectedCount => _selectedIds.length;
// 进入选择模式
void enterSelectionMode(int initialId) {
_isSelectionMode = true;
_selectedIds.add(initialId);
notifyListeners();
}
// 退出选择模式
void exitSelectionMode() {
_isSelectionMode = false;
_selectedIds.clear();
notifyListeners();
}
// 切换某个条目的选中状态
void toggleSelection(int id) {
if (_selectedIds.contains(id)) {
_selectedIds.remove(id);
if (_selectedIds.isEmpty) {
_isSelectionMode = false; // 全部取消后自动退出选择模式
}
} else {
_selectedIds.add(id);
}
notifyListeners();
}
// 全选
void selectAll() {
_selectedIds.addAll(_allMemos.map((m) => m.id!));
notifyListeners();
}
// 取消全选
void deselectAll() {
_selectedIds.clear();
_isSelectionMode = false;
notifyListeners();
}
// 批量删除
Future<void> deleteSelected() async {
for (final id in _selectedIds) {
await DatabaseHelper.instance.deleteMemo(id);
}
_selectedIds.clear();
_isSelectionMode = false;
await loadMemos();
}
}
核心设计要点:
_selectedIds是一个Set<int>,O(1) 的查找和删除效率- 所有选中项都被取消后自动退出选择模式
deleteSelected是异步方法,逐条删除后重新加载数据
AppBar 的动态切换
选择模式下,AppBar 从"备忘录列表"变为"批量操作栏":
class MemoListPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: Consumer<MemoProvider>(
builder: (context, provider, _) {
if (provider.isSelectionMode) {
return _buildSelectionAppBar(provider);
}
return _buildNormalAppBar(context);
},
),
body: _buildBody(),
);
}
选择模式 AppBar
AppBar _buildSelectionAppBar(MemoProvider provider) {
return AppBar(
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => provider.exitSelectionMode(),
),
title: Text('已选择 ${provider.selectedCount} 项'),
actions: [
// 全选/取消全选
TextButton(
onPressed: () {
if (provider.selectedCount == provider.memos.length) {
provider.deselectAll();
} else {
provider.selectAll();
}
},
child: Text(
provider.selectedCount == provider.memos.length ? '取消全选' : '全选',
style: const TextStyle(color: Colors.white),
),
),
// 删除按钮
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: provider.selectedCount > 0
? () => _confirmBatchDelete(context, provider)
: null,
),
],
backgroundColor: const Color(0xFFE53935), // 红色背景警告态
);
}
红色背景的 AppBar 在视觉上给用户一个明确的信号——“你在操作模式中,小心”。
删除确认对话框
批量删除是不可逆操作,必须有确认环节:
void _confirmBatchDelete(BuildContext context, MemoProvider provider) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除选中的 ${provider.selectedCount} 条备忘录吗?此操作不可撤销。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('取消'),
),
TextButton(
onPressed: () {
provider.deleteSelected();
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已删除 ${provider.selectedCount} 条备忘录')),
);
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('删除'),
),
],
),
);
}
列表项的多选 UI
选择模式下的列表项需要显示复选框:
Widget _buildMemoItem(Memo memo, MemoProvider provider) {
final isSelected = provider.selectedIds.contains(memo.id);
return GestureDetector(
onLongPress: () {
if (!provider.isSelectionMode) {
provider.enterSelectionMode(memo.id!);
HapticFeedback.mediumImpact(); // 触觉反馈
}
},
onTap: () {
if (provider.isSelectionMode) {
provider.toggleSelection(memo.id!);
} else {
// 正常模式:打开编辑页
Navigator.pushNamed(context, '/memo/edit', arguments: memo.id);
}
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
color: isSelected
? const Color(0xFF4DB6AC).withOpacity(0.08)
: Colors.transparent,
child: Row(
children: [
// 选择模式下显示复选框
if (provider.isSelectionMode)
Padding(
padding: const EdgeInsets.only(left: 16),
child: Icon(
isSelected
? Icons.check_circle
: Icons.radio_button_unchecked,
color: isSelected
? const Color(0xFF4DB6AC)
: Colors.grey.shade400,
),
),
// 卡片内容
Expanded(child: MemoCard(memo: memo)),
],
),
),
);
}
关键交互细节:
- 长按进入选择模式:
onLongPress触发enterSelectionMode,同时播放HapticFeedback.mediumImpact()触觉反馈 - 点击行为分流:选择模式下点击 = 勾选/取消,正常模式下点击 = 打开编辑页
- 选中背景色:
AnimatedContainer的color从透明过渡到 8% 透明度主题色 - 复选框 icon:用
check_circle/radio_button_unchecked替代 MaterialCheckboxwidget,风格更轻量
防止内存泄漏:批量删除中的 mounted 检查
批量删除是异步操作,删除过程中用户可能导航到其他页面:
Future<void> deleteSelected() async {
final idsToDelete = Set<int>.from(_selectedIds);
_selectedIds.clear();
_isSelectionMode = false;
notifyListeners();
for (final id in idsToDelete) {
await DatabaseHelper.instance.deleteMemo(id);
}
await loadMemos();
// loadMemos 会触发 notifyListeners()
}
注意:此处先把 _selectedIds 拷贝一份再清空,UI 立即更新(移除红色 AppBar),然后在后台逐条删除。如果用户在删除过程中导航离开,widget 已被 dispose,loadMemos 中如果有 notifyListeners() 调用,listener 不会引发崩溃——Provider 已经处理了这种情况。
鸿蒙兼容性
批量删除的实现完全在 Dart 层:
Set<int>状态管理:Dart 标准库HapticFeedback:Flutter services 层,在鸿蒙上调用 OHOS 振动 API(需确认flutter_ohos引擎是否实现了振动能力)showDialog/AlertDialog:Material 组件,纯 Flutter 实现
如果鸿蒙设备不支持 HapticFeedback,mediumImpact() 会静默失败(不会抛异常)。这是一个很好的防御性编程实践。
总结
多选批量删除模式的关键在于状态转换:
- 长按 →
isSelectionMode = true,AppBar 切换为红色操作栏 - 点击 →
toggleSelection(id),Set<int>增删元素 - 点击删除 → 确认对话框 → 逐条删除 → 重新加载
- 取消/完成 →
exitSelectionMode()清空状态
总共约 100 行 Provider 代码,60 行 UI 代码,实现了一套符合移动端直觉的批量操作交互。
完整项目代码见:todo_flutter_harmony
更多推荐


所有评论(0)