鸿蒙新特性:SwipeAction 滑动操作——构建任务收件箱与手势交互 📅 2026/7/7 3:35:10 滑动操作Swipe Action是移动端最自然、最高效的交互模式之一。从 iOS 邮件应用的滑动归档到微信的滑动删除聊天从 Gmail 的滑动标记已读到 Things 的滑动安排日程——滑动手势已经成为用户无需思考的肌肉记忆。HarmonyOS NEXT ArkUI 在 ListItem 组件上提供了.swipeAction()属性让开发者用少量代码就能实现丰富的滑动交互菜单。然而SwipeAction 不只是加几个按钮那么简单。在哪一侧放置什么操作、操作区的视觉设计、滑动后状态的更新时机、与列表排序和筛选的配合——这些设计决策决定了滑动交互的可用性和流畅度。本文将通过一个任务收件箱实战案例深入讲解 SwipeAction 的设计原理、实现细节和最佳实践。关键词HarmonyOS、ArkUI、SwipeAction、滑动操作、手势交互、任务管理一、SwipeAction 组件基础1.1 API 概述SwipeAction 是 ListItem 的扩展属性通过.swipeAction()方法配置。它支持在列表项的两侧各配置一组操作按钮用户通过水平滑动手势将其拉出ListItem(){// 列表项内容}.swipeAction({start:{// 起始侧LTR 布局下为左侧builder:(){/* 操作按钮 */},actionAreaDistance:80// 操作区宽度vp},end:{// 结束侧LTR 布局下为右侧builder:(){/* 操作按钮 */},actionAreaDistance:160}})核心属性说明start在阅读方向起始侧的操作区。从左到右LTR布局中这是左侧右到左RTL布局中则是右侧。用户将列表项向右滑动时显露该区域。end在阅读方向结束侧的操作区。LTR 中为右侧用户将列表项向左滑动时显露。builder一个Builder函数构建操作区的 UI 内容。支持 Row、Text、自定义组件等。actionAreaDistance操作区展开后的宽度vp。决定了用户需要滑多长距离才能完全显露出操作按钮。1.2 两侧操作的设计哲学在移动端设计中左侧滑动和右侧滑动承载不同的语义左滑显露 end 侧——这是最符合右手拇指操作习惯的方向通常放置破坏性操作删除和中性操作归档、标记已读。原因有三一是这个方向的滑动最顺手二是左滑是推开当前内容的隐喻推开 移除/删除三是用户的下意识滑动方向。Apple 的 Human Interface Guidelines 和 Google 的 Material Design 都推荐将主要操作放在左滑方向。右滑显露 start 侧——右滑通常放置优先操作置顶、收藏、标记重要。这是拉近的隐喻拉回 想要/需要用户需要更大的意图来执行这个方向的手势尤其是左手用户。在邮件应用中右滑通常用于标记未读或移动到文件夹。在我们的任务收件箱中右滑start 侧→ 置顶 / 取消置顶 左滑end 侧 → 完成 删除这个布局符合上述设计哲学左滑是最自然的操作放置最常用的完成和删除右滑是次常用的置顶只有需要优先级调整时才触发。1.3 操作区的视觉设计操作区的设计直接影响用户的操作信心。一个设计糟糕的操作区如颜色含义模糊、按钮过大或过小、缺少文字标注会使用户犹豫甚至误操作。本文的操作区设计原则绿色完成按钮左滑第一位使用#00B894背景这是自然、安全、积极的颜色信号。放在最靠近内容的一侧即滑动距离最小的位置因为完成比删除更频繁使用用户不必滑太远就能触发。红色删除按钮左滑第二位使用#FF4D4F背景这是全世界通用的危险/删除颜色信号。放在更远的位置需要用户付出额外的滑动距离这种摩擦设计减少了误删的可能性。蓝色置顶按钮右滑使用#1677FF背景这是操作/重要的颜色信号。单独占用右滑方向不与左滑操作竞争视觉注意力。每个按钮使用 80vp 宽度actionAreaDistance两个左滑按钮共 160vp。这个宽度在足够容纳文字和不会过度遮挡内容之间取得了平衡。1.4 与系统手势的协调SwipeAction 与系统级的边缘滑动手势如来福手势返回需要协调。HarmonyOS 的 SwipeAction 已经处理了手势冲突——当列表项有滑动操作时系统会优先响应列表项级别的手势。如果用户在列表区域内滑动触发 SwipeAction如果在屏幕边缘滑动触发系统返回手势。开发者无需额外处理手势冲突这是框架层的默认行为。但如果列表项同时有自定义手势如.gesture()需要注意手势优先级的管理。二、任务收件箱的设计2.1 功能规划任务收件箱是一个完整的任务管理工具融合了滑动操作、状态切换、优先级管理和列表筛选滑动操作左滑显示完成“删除”右滑显示置顶点击完成勾选复选框切换完成/未完成状态优先级标签高/中/低三级用颜色区分置顶排序已置顶任务自动排到列表顶部分类筛选全部 / 未完成 / 已完成统计面板全部任务 / 未完成 / 已完成数量添加任务快捷添加新任务到列表底部空状态筛选结果为空时显示相应提示2.2 数据结构设计interfaceTaskItem{id:number;title:string;// 任务标题desc:string;// 任务描述priority:string;// high | medium | lowcolor:string;// 优先级对应的颜色completed:boolean;// 是否已完成pinned:boolean;// 是否置顶}关键字段的设计考量priority color 组合使用枚举式字符串high/medium/low标识优先级同时存储对应的显示颜色。颜色随优先级确定红→高、橙→中、绿→低在任务列表中用作左侧色条。优先级的映射关系优先级颜色标签含义high#FF4D4F红高需优先处理通常包含截止日期medium#E09F3E橙中正常处理按计划推进即可low#00B894绿低有空再处理无紧急时限pinned 置顶标记布尔值与 completed 互斥——已完成的任务不能置顶。这是合理的业务规则已完成的任务在逻辑上不需要优先级置顶是对未完成任务的空间优待。completed布尔值标记完成是任务管理中最核心的状态流转。完成后的视觉变化包括标题变灰、添加删除线、优先级标签变灰、左侧色条变灰、pin 标记消失。2.3 排序与筛选任务列表的展示逻辑包含两个步骤先排序再筛选。排序getSorted置顶的任务排在前面未置顶的排在后面。同组内的任务保持原始插入顺序不变getSorted():TaskItem[]{letresultthis.tasks.slice();result.sort((a:TaskItem,b:TaskItem){if(a.pinned!b.pinned)return-1;if(!a.pinnedb.pinned)return1;return0;});returnresult;}注意Array.sort()是稳定排序HarmonyOS 的 JS 引擎保证所以同组任务保持原始顺序。这对用户体验很重要——如果排序不稳定用户会觉得列表在跳动。筛选getFiltered在排序后的数组上应用分类筛选——全部返回全部未完成只返回completed false的已完成只返回completed true的。排序和筛选都不缓存——每次调用动态计算。对于任务收件箱这种规模通常 50 条实时计算性能完全够用不需要引入缓存的复杂性。2.4 统计面板三个统计数字全部任务总数、未完成红色、已完成绿色。未完成用红色数字而非常规的深色——这是一种焦虑信号设计。红色数字会促使用户关注待办事项而下方的文字标签未完成保持了中性灰色。颜色只在数字上使用不像整行都是红色那样有压迫感但足够引起注意。已完成用绿色——这是一种完成信号给用户正向反馈“看你已经做了这么多”。绿色数字的组合也是一种隐性的成就感激励。三、核心功能实现3.1 切换完成状态点击复选框或左滑完成按钮触发的核心操作toggleComplete(id:number):void{letidxthis.findIdx(id);if(idx0)return;letitemthis.copyItem(this.tasks[idx]);item.completed!item.completed;item.pinneditem.completed?false:item.pinned;this.applyChange(idx,item);}关键细节当任务被标记为完成时自动取消置顶pinned false。已完成的任务没有理由占用顶部空间——这是数据完整性的保证。当任务被反标记从已完成变回未完成置顶状态保持不变不会自动恢复置顶——这是一种保守策略避免意外恢复用户可能已经忘记的置顶状态。copyItem和applyChange方法实现了 ArkUI 的不可变状态更新copyItem(src:TaskItem):TaskItem{return{id:src.id,title:src.title,desc:src.desc,priority:src.priority,color:src.color,completed:src.completed,pinned:src.pinned};}applyChange(idx:number,item:TaskItem):void{letcopythis.tasks.slice();copy[idx]item;this.taskscopy;}这是我们在之前文章中详细讨论过的Statearray 响应式更新模式——新数组引用 新对象引用 触发重渲染。3.2 删除任务左滑删除按钮触发deleteTask(id:number):void{letresult:TaskItem[][];for(leti0;ithis.tasks.length;i){if(this.tasks[i].id!id){result.push(this.tasks[i]);}}this.tasksresult;}构建一个新数组跳过被删除的项赋值给this.tasks。因为生成了全新的数组引用State会检测到变化并重渲染。删除后的动画由 List 组件自动处理列表项的移除动画无需手动添加过渡效果。3.3 置顶切换右滑置顶按钮触发togglePin(id:number):void{letidxthis.findIdx(id);if(idx0)return;letitemthis.copyItem(this.tasks[idx]);if(!item.completed){item.pinned!item.pinned;this.applyChange(idx,item);}}!item.completed守卫确保已完成任务不能置顶。切换置顶后列表会因为getSorted()的排序逻辑自动将该项目移到顶部如果是置顶操作或回到原位如果是取消置顶。这是数据驱动 UI 的优势——不需要手动操作 DOM 或调整列表位置只需改变数据的状态视图会自动根据排序规则响应。3.4 SwipeAction 的实现SwipeAction 使用Builder定义操作区 UIBuilderendActionBuilder(item:TaskItem){Row(){if(!item.completed){Row(){Text(完成).fontSize(13).fontColor(#FFFFFF).fontWeight(FontWeight.Medium)}.width(80).height(100%).justifyContent(FlexAlign.Center).backgroundColor(#00B894).borderRadius({topLeft:10,bottomLeft:10}).onClick((){this.toggleComplete(item.id);})}Row(){Text(删除).fontSize(13).fontColor(#FFFFFF).fontWeight(FontWeight.Medium)}.width(80).height(100%).justifyContent(FlexAlign.Center).backgroundColor(#FF4D4F).borderRadius({topRight:10,bottomRight:10}).onClick((){this.deleteTask(item.id);})}.height(100%)}BuilderstartActionBuilder(item:TaskItem){Row(){Row(){Text(item.pinned?取消置顶:置顶).fontSize(13).fontColor(#FFFFFF).fontWeight(FontWeight.Medium)}.width(80).height(100%).justifyContent(FlexAlign.Center).backgroundColor(#1677FF).borderRadius(10).onClick((){this.togglePin(item.id);})}.height(100%)}在 ListItem 上应用ListItem(){// ... 任务内容}.swipeAction({end:{builder:(){this.endActionBuilder(item);},actionAreaDistance:160},start:{builder:(){this.startActionBuilder(item);},actionAreaDistance:80}})几个值得注意的实现细节条件显示完成按钮如果任务已经完成了左滑菜单只显示删除隐藏完成。这避免了已完成任务出现再完成一次的尴尬操作。对应地actionAreaDistance仍保持 160vp——即使完成按钮隐藏了操作区宽度不变保持列表项行为的一致性。按钮宽度 80vp两个按钮各 80vp 宽是经过权衡的选择。太窄 60vp文字放不下或需要缩小字号太宽 100vp过度遮挡列表内容且滑动距离过长。80vp 足够容纳两个中文字符和左右内边距。圆角处理完成按钮使用borderRadius({ topLeft: 10, bottomLeft: 10 })左侧圆角删除按钮使用borderRadius({ topRight: 10, bottomRight: 10 })右侧圆角两个按钮拼在一起形成一个完整的圆角矩形。右侧的置顶按钮使用全圆角borderRadius(10)。按钮文字动态切换置顶按钮根据当前状态显示置顶或取消置顶给予用户明确的操作预期。3.5 复选框除了 SwipeAction任务还提供了点击复选框来切换完成状态Row(){if(item.completed){Text(✓).fontSize(14).fontColor(#FFFFFF).fontWeight(FontWeight.Bold)}}.width(22).height(22).justifyContent(FlexAlign.Center).borderRadius(11).border({width:2,color:item.completed?#00B894:#CCCCDD}).backgroundColor(item.completed?#00B894:#00000000).margin({left:12}).onClick((){this.toggleComplete(item.id);})复选框的设计未完成22x22 空心圆灰色边框#CCCCDD无背景色无文字已完成22x22 实心圆绿色背景#00B894白色对勾 ✓空心→实心的视觉转变明确、直接不需要额外的动画。点击区域为 22x22padding符合 iOS 和 Material Design 推荐的最小触摸目标44pt。四、完整交互流程4.1 初始状态页面加载后展示 9 条任务涵盖三种优先级和两种完成状态。准备技术分享和完成季度报告两条任务带有 置顶标记排在列表顶部。统计面板显示全部任务 9、未完成 7、已完成 2。4.2 左滑标记完成在修复登录模块 Bug上左滑展现绿色完成按钮和红色删除按钮。点击完成——该任务左侧色条由红色变为灰色#CCCCCC优先级标签由红色变为灰色标题文字变灰并添加删除线复选框由空心变为绿色实心对勾统计面板未完成 6、已完成 3再次左滑该任务只有删除按钮不再显示完成因为已经完成了。4.3 左滑删除在整理技术文档上左滑点击红色删除按钮。该任务从列表中移除。统计面板更新。4.4 右滑置顶在对接第三方推送 SDK上右滑展现蓝色置顶按钮。点击——该任务立即跳到列表顶部排在已有置顶项后面 图标出现。4.5 点击复选框点击更新 API 文档左侧的复选框直接切换完成/未完成无需滑动。这是滑动之外的另一条操作路径适用于快速勾掉多个任务的场景。4.6 筛选点击筛选栏的已完成列表只显示已完成的任务。点击未完成只显示未完成的任务。切换回全部恢复完整列表。空状态时显示对应提示文字。4.7 添加任务点击标题栏的添加任务一条新任务追加到列表底部优先级根据已有数量自动轮换红/橙/绿。新任务可被正常滑动、勾选、置顶和删除。五、SwipeAction 的设计指南5.1 操作按钮的数量每个方向start/end最多放2 个按钮。超过 2 个会让操作区过宽 200vp滑动距离过长且用户在一瞥中难以快速抉择点哪个。如果确实需要 3 个以上的操作考虑仅保留 2 个最常用的其余放进更多菜单让用户自定义常用操作使用长按触发更多操作菜单本文的 end 侧仅放 2 个按钮完成 删除start 侧仅 1 个置顶都是扫一眼就能决定的数量。5.2 操作按钮的颜色颜色是滑动操作的情感编码需要与操作的含义匹配红色系#FF4D4F删除、取消、拒绝——破坏性操作绿色系#00B894完成、通过、确认——积极操作蓝色系#1677FF标记、置顶、编辑——中性操作灰色系#9999AA归档、隐藏——弱操作橙色系#E09F3E提醒、稍后——延迟操作永远不要在滑动操作中使用含义模糊的颜色如紫色配删除、绿色配取消这会造成认知失调。5.3 操作按钮应有明确文字标注仅依赖图标如 删除不够直观——不同文化背景的用户对图标的解读不同。永远在滑动操作按钮上同时使用图标和文字或纯文字。本文选择纯文字方案确保零歧义。5.4 操作后是否自动关闭SwipeAction 在点击按钮后是否自动收回列表项答案是取决于操作的性质。如果操作会改变列表内容删除、移动SwipeAction 会自动关闭因为 ListItem 已在列表中消失或重排。如果操作不改变列表内容如标记已读但列表项仍在原位置ArkUI 默认不会自动关闭 SwipeAction。我们需要考虑是否手动关闭。在本文的实现中所有操作都改变了数据状态完成/删除/置顶 触发重渲染列表项会在State更新后重绘SwipeAction 自然关闭。这是利用框架行为的干净方案。如果需要在某些场景下手动关闭 SwipeAction可以通过给 ListItem 的 key 强制变化来触发重绘例如在操作后改变一个 counter 并作为 key 的一部分。5.5 反馈确认对于破坏性操作删除在 SwipeAction 中直接执行删除通常是可以接受的——因为滑动手势本身已经是一种确认行为。用户在滑动时已经看到了红色的删除按钮再点击它需要明确的意图。这与在工具栏上按删除 — 弹确认框的模式不同。但如果删除的数据是不可恢复的如删除账号、删除支付记录还是应该在 SwipeAction 按钮点击后弹出二次确认。本文的 Demo 中的任务删除是可接受的无确认删除因为任务数据是临时性的 Demo 数据用户可以通过添加任务重新创建在实际产品中建议提供撤销能力如底部 SnackBar “任务已删除点击撤销”这比弹确认框的用户体验更好。5.6 适配暗黑模式SwipeAction 的操作区按钮是自定义的Builder不会自动适配暗黑模式。如果在暗黑模式下按钮背景色需要调整——例如红色删除按钮在暗黑模式下应使用稍暗的红色#D9363E而非#FF4D4F避免在黑色背景上过于刺眼。六、进阶扩展方向6.1 撤销删除结合系统 SnackBar 或自定义 Toast 实现删除撤销deleteTask(id:number):void{letdeletedthis.tasks[this.findIdx(id)];// 执行删除// ...// 显示撤销提示this.showUndo(deleted);}showUndo(item:TaskItem):void{// 显示底部 SnackBar「{item.title}」已删除 撤销// 点击撤销 → 将 item 重新插入原位置// 3 秒后自动消失}6.2 拖拽排序结合 ListItem 的拖拽能力.onDragStart、.onDrop允许用户通过长按拖拽手动调整任务顺序。拖拽排序与置顶排序互不冲突——拖拽设置的是手动顺序置顶是额外的优先级加权。6.3 分组视图对于大量任务 20 条引入分组维度——按项目、按优先级、按截止日期分组。SwipeAction 在每个分组内独立工作。6.4 多选批量操作长按进入多选模式批量完成、批量删除、批量设置优先级。多选模式下SwipeAction 应暂时禁用——列表项的手势区域被复选框占据避免滑动操作与选择操作冲突。6.5 自定义滑动阈值虽然actionAreaDistance设置了操作区的宽度但实际触发滑动需要用户滑动到什么程度ArkUI 使用一个内置的阈值通常是操作区宽度的 30%超过后自动弹开到完整宽度。如果需要调整阈值目前 API 不直接支持但可以通过组合手势.gesture().swipeAction()实现更精细的控制。七、总结本文通过任务收件箱这个实战案例全面讲解了 SwipeAction 滑动操作组件的设计原理和实现细节。核心知识点包括SwipeAction APIstart/end 两侧、builder 构建器、actionAreaDistance 宽度控制两侧操作的设计哲学左滑end 最常用操作完成/删除右滑start 次要操作置顶操作按钮的视觉设计颜色语义编码绿完成、红删除、蓝置顶、宽度权衡、圆角处理状态管理不可变更新slice copyItem applyChange、任务/完成/置顶状态流转排序与筛选置顶优先排序 分类筛选 统计面板SwipeAction 的可用性设计指南按钮数量 ≤ 2、颜色与操作匹配、文字标注、撤销机制SwipeAction 的魅力在于它符合直觉——滑动是人最自然的手势之一将它用于任务管理是顺势而为。但好的滑动交互不止于能滑动更在于在正确的方向放置正确的操作、用正确的颜色传递正确的情感、在正确的时机给用户正确的反馈。三个正确叠加才能从功能完善做到体验舒适。