HarmonyOS ArkUI List 侧滑删除:ListItem.swipeAction 实践
HarmonyOS ArkUI List 的侧滑删除 用 ListItem 的 swipeAction + endIcon 的做法
鸿蒙开发的朋友看这篇,侧滑删除从 swipeAction 配置、阈值手感到删除后刷新列表的坑都讲全了,代码能直接抄。
文章讲解鸿蒙 List 组件实现侧滑删除的做法,核心是 ListItem 的 swipeAction 配置 start 和 end 两个 builder。作者给出 actionAreaDistance 设 100vp 的手感参考,指出 50vp 太敏感、150vp 太费力。交互上支持滑开后点击触发 onAction,滑过 2 倍阈值(200vp)可直接触发省一次点击。文中还强调删除后要整体数组重新赋值而非 splice,否则 ArkUI List 可能不触发 rebuild。
HarmonyOS ArkUI List 的侧滑删除 用 ListItem 的 swipeAction + endIcon 的做法
鸿蒙的 List 组件 自带侧滑交互 ,但 API 不直观——是通过 ListItem.swipeAction 配 start 和 end 两个 builder 实现的。做了几个 app 的列表都要加" 侧滑删除 "和" 侧滑归档 ",走通后发现 pattern 很固定。记录一下 swipeAction + ListItem 的做法,顺便分享给有需要的朋友。 一、基础 pattern @Entry @Component struct AssetListPage { @State items : Asset [] = [] build ( ) { List ({ space : 10 }) { ForEach ( this . items , ( item: Asset ) => { ListItem () { AssetRow ({ asset : item }) } . swipeAction ({ end : { builder : () => this . endSwipeBuilder (item), actionAreaDistance : 100 , onAction : () => this . deleteItem (item. id ), }, }) }, ( item: Asset ) => item. id . toString ()) } } @Builder endSwipeBuilder ( item: Asset ) { Row () { Column () { Image ($r( 'app.media.ic_delete' )). width ( 24 ). height ( 24 ) Text ( '删除' ). fontSize ( 12 ). fontColor ( Color . White ) } . width ( 80 ) . height ( '100%' ) . justifyContent ( FlexAlign . Center ) . backgroundColor ( '#EF4444' ) } } private async deleteItem ( id : number ): Promise < void > { await this . dao . delete (id) this . items = await this . dao . getAll () } } 关键字段 : end: { builder, actionAreaDistance, onAction } : 侧滑到左边 (end = 滑到结束方向)。 builder: 滑开后显示的 UI actionAreaDistance: 滑开阈值 (100 vp) onAction: 滑开后用户再次点击时触发 start : 滑到右边 (可选)。本案没用。 二、侧滑的交互语义 用户动作 → 效果 : 动作 结果 轻滑 (<100vp) 回弹,不显示 action 滑开 >100vp 停住,显示 action 滑开后点 action 触发 onAction 滑开后点列表本身 回弹 滑过阈值 2 倍 (>200vp) 直接触发 onAction (省一次点击) 用户体验 : 怕误触 → 要 滑开 + 再点 (两步) 效率党 → 直接滑到底 (一步) 本案的 100 vp 阈值 : 手感合适 。 50 vp 太敏感 150 vp 太费力 三、双侧 action(左删 右归档) . swipeAction ({ start : { builder : () => this . startSwipeBuilder (item), actionAreaDistance : 100 , onAction : () => this . archiveItem (item. id ), }, end : { builder : () => this . endSwipeBuilder (item), actionAreaDistance : 100 , onAction : () => this . deleteItem (item. id ), }, }) @Builder startSwipeBuilder ( item: Asset ) { Column () { Image ($r( 'app.media.ic_archive' )). width ( 24 ). height ( 24 ) Text ( '归档' ). fontSize ( 12 ). fontColor ( Color . White ) } . width ( 80 ) . height ( '100%' ) . backgroundColor ( '#3B82F6' ) // 蓝色 } 左右两套 action : start (右滑): 归档 (蓝) end (左滑): 删除 (红) 色彩约定 : 红 → 危险 / 删除 蓝 → 操作 / 归档 绿 → 确认 / 完成 独立开发者的建议 : 红色永远是删除 。不要 用红色做其他 action (用户习惯了)。 四、滑动删除后的 UI 更新 private async deleteItem ( id : number ): Promise < void > { await this . dao . delete (id) this . items = await this . dao . getAll () // 👈 整数组重新赋值 } 关键 : 整数组重新赋值 ,不是 原地 splice 。 不要 : // ❌ 原地改 const idx = this . items . findIndex ( i => i. id === id) this . items . splice (idx, 1 ) ArkUI 的 List 对数组引用响应 ,splice 可能 不触发 rebuild 。 正解 : 整数组赋值 (本案)或 用 immutable 的 filter : this . items = this . items . filter ( i => i. id !== id) ForEach 的 key (第三参数): ForEach ( this . items , ( item: Asset ) => { // ... }, ( item: Asset ) => item. id . toString ()) key 的作用 : ArkUI 识别哪些 item 变了 ,只 rebuild 变化的。 没 key → 全部 rebuild (卡) 有 key → 只 rebuild 变化的 (流畅) 独立开发者的建议 : ForEach 的 key 永远指定 ,用 id 字段 。 五、动画:删除时淡出 朴素做法 :删了直接消失, 生硬 。 优化 : animateTo 淡出 + 移除 。 private async deleteItem ( id : number ): Promise < void > { // 1. 先标记删除中 const idx = this . items . findIndex ( i => i. id === id) this . items [idx]. pendingDelete = true // 2. 等动画完成 animateTo ({ duration : 300 , curve : Curve . EaseOut }, () => { this . items = this . items . filter ( i => !i. pendingDelete ) }) // 3. 异步同步 DB await this . dao . delete (id) } UI : ListItem () { AssetRow ({ asset : item }) } . opacity (item. pendingDelete ? 0 : 1 ) . height (item. pendingDelete ? 0 : undefined ) // 👈 高度塌陷 . animation ({ duration : 300 }) 用户看到 : item 淡出 + 列表平滑合拢 。 独立开发者的建议 : 删除动画 是 app 精致度的关键 。1-2 行代码的差别, 用户感知的 " app 质感 " 翻倍 。 六、确认对话框:防止误删 重要数据 (如联系人、笔记) 不要滑开就删 : . swipeAction ({ end : { builder : () => this . endSwipeBuilder (item), onAction : () => { promptAction. showDialog ({ title : '删除确认' , message : `确定删除「 ${item.title} 」吗?此操作不可撤销。` , buttons : [ { text : '取消' , color : '#999' }, { text : '删除' , color : '#EF4444' }, ], }). then ( ( result ) => { if (result. index === 1 ) { this . deleteItem (item. id ) } }) }, }, }) 用户 flow : 滑开 → 点删除 → 弹对话框 → 再确认 。 三步 。 不要 : 不重要的数据也弹对话框 。打断用户。 决策 : 轻量数据 (to-do / 书摘): 滑开直接删 (可撤销) 重要数据 (联系人 / 账单): 滑开 + 确认 七、Undo(撤销删除) 滑开直接删 + 显示 undo toast : private async deleteItem ( id : number ): Promise < void > { const deleted = this . items . find ( i => i. id === id) if (!deleted) return this . items = this . items . filter ( i => i. id !== id) await this . dao . delete (id) // 显示 undo promptAction. showToast ({ message : '已删除。点此撤销' , duration : 5000 , // 鸿蒙 Toast 不支持 action button,用 Snackbar 替代 }) this . lastDeleted = deleted this . lastDeletedTime = Date . now () } private async undoLast (): Promise < void > { if (! this . lastDeleted ) return if ( Date . now () - this . lastDeletedTime > 5000 ) return // 5 秒内才能撤销 await this . dao . insert ( this . lastDeleted ) this . items = await this . dao . getAll () this . lastDeleted = null } Snackbar / 自定义 Dialog 带按钮 。鸿蒙的内置 Toast 没按钮,要 自画 。 撤销窗口 5 秒 : 平衡 。 2 秒: 用户反应不过来 10 秒: 太长 ,用户已经走了 八、空列表的 Empty State if ( this . items . length === 0 ) { Column () { Image ($r( 'app.media.ic_empty' )). width ( 80 ). height ( 80 ) Text ( '还没有数据' ). fontSize ( 14 ). fontColor ( '#999' ) Button ( '添加第一条' ). onClick ( () => this . openAddSheet ()) } . width ( '100%' ) . height ( '100%' ) . justifyContent ( FlexAlign . Center ) } else { List ({ space : 10 }) { ... } } 空列表要有引导 : 图标 + 文案 + 按钮 用户知道 要去哪里添加 不要 : 只显示白屏 。用户以为 app 坏了。 踩坑清单 侧滑用 swipeAction({ start, end }) actionAreaDistance 100 vp 手感最好 红色 = 删除,蓝色 = 归档, 约定俗成 删除要 整数组赋值 (or filter),不要 splice ForEach 的 key 必须指定 加 淡出动画 提升质感 重要数据要 确认 dialog 轻量数据加 undo toast 空列表要有 引导 (图标 + 文案 + 按钮) 尾声 侧滑交互 是 列表 app 的标准动作 。做好这一个交互 → 用户觉得" 像原生 iOS Mail "(高质感);做不好 → 用户觉得" 像半成品 "。 独立开发者的建议 : 所有 List 页都加侧滑 (哪怕只有删除) 配 删除动画 轻量数据加 undo 空列表加 引导 对标 : iOS Mail: 侧滑归档 + 侧滑删除 的鼻祖 Gmail: 类似 pattern 鸿蒙 ArkUI: 内置了这个 API ,比 Android 的 ItemTouchHelper 省心 鸿蒙的 List 比 iOS 的 UITableView 更现代 (声明式)。独立开发者 用 ArkUI 的 List 开发速度比 iOS 快 30-50% 。