HarmonyOS《柚兔学伴》项目实战09-待办列表 UI——List+ForEach+滑动删除
09数据持久化只是第一步将数据以优雅的交互形式呈现给用户才是最终目标。本文将围绕TodoView.ets讲解如何使用 ArkUI 的ListForEach渲染待办列表、swipeAction实现滑动删除、CustomImageToggle处理完成状态以及空状态、计时器、诗词阅读等辅助功能的集成。1. TodoView 组件概览Componentexportstruct TodoView{privatepageContext:PageContextAppStorage.get(pageContext)asPageContext;StorageProp(GlobalInfoModel)globalInfoModel:GlobalInfoModelAppStorage.get(GlobalInfoModel)!;Statearr:TodoItem[][]privatetodoDb:TodoDatabasenewTodoDatabase(getContext());StateisTodoEmpty:booleanfalse// ... 其他状态}核心状态State arr: TodoItem[]待办事项数组驱动列表渲染。StorageProp(GlobalInfoModel)全局设备信息获取状态栏高度等参数。todoDb数据库实例负责 CRUD 操作。isTodoEmpty控制空状态与列表状态的切换。2. 初始化数据库与示例数据asyncaboutToAppear():Promisevoid{awaitthis.todoDb.initialize();letisFirstPreferencesUtil.getBooleanSync(isFirst,true)if(isFirst){this.initDemoTodoList();}else{this.arrawaitthis.todoDb.getAllTodos();}this.getPoem()font.registerFont({familyName:kaiti,familySrc:$rawfile(font/kaiti.ttf)})}初始化流程初始化数据库——await this.todoDb.initialize()。判断首次启动——通过PreferencesUtil读取标记。首次启动调用initDemoTodoList()添加示例数据。非首次启动从数据库加载所有待办事项。注册楷体字体——font.registerFont注册自定义字体供诗词显示使用。2.1 示例数据privateinitDemoTodoList(){this.arr.push({id:1,time:08:00,content:(示例)晨读,isCompleted:true,date:,dayOfWeek:});this.arr.push({id:2,time:09:00,content:(示例)吃早饭,isCompleted:false,date:,dayOfWeek:});this.arr.push({id:3,time:10:00,content:(示例)语文作业,isCompleted:false,date:,dayOfWeek:});}首次用户看到三条示例待办帮助他们快速理解应用功能。3. List ForEach 渲染待办列表3.1 空状态与列表状态切换if(this.isTodoEmpty){this.buildEmptyList()}else{this.buildTodoList()}3.2 空状态展示BuilderbuildEmptyList(){Column({space:10}){Text(当前暂无待办事项).fontColor($r(app.color.color_sub_text)).fontSize(12)Button(添加).height(36).fontSize(14).backgroundColor($r(app.color.app_primary)).fontColor($r(app.color.color_card)).borderRadius(25).onClick((){this.isShowTasktrue;});}.backgroundColor($r(app.color.color_card)).layoutWeight(1).justifyContent(FlexAlign.Center).width(100%).margin({bottom:40}).borderRadius(12)}当列表为空时显示提示文字和「添加」按钮引导用户创建第一条待办。3.3 列表渲染BuilderbuildTodoList(){List({space:12,scroller:this.scrollerForList}){ForEach(this.arr,(item:TodoItem,index:number){ListItem(){Row(){Column(){Text(item.content).layoutWeight(1).fontWeight(FontWeight.Bold)Row({space:6}){Image($r(app.media.ic_time_icon)).width(18);Text(item.time).fontColor($r(app.color.color_text_light));};}.layoutWeight(1).alignItems(HorizontalAlign.Start);CustomImageToggle({isOn:item.isCompleted,onToggleChange:(value){// 完成状态切换逻辑}});}.width(100%).height(100%);}.swipeAction({end:{builder:(){this.itemEnd(index);},actionAreaDistance:40,}}).height(80).padding({left:12,top:5,bottom:5,right:12}).borderRadius(12).backgroundColor($r(app.color.color_card));},(item:TodoItem)item.id?.toString());}.listDirection(Axis.Vertical).scrollBar(BarState.Off).friction(0.6).margin({bottom:40}).width(100%)}关键组件解析List可滚动列表容器space: 12设置列表项间距。ForEach渲染列表数据第三个参数是键值生成函数(item) item.id?.toString()用于高效 diff 更新。ListItem列表项容器支持swipeAction滑动操作。scrollerForListScroller控制器支持编程式滚动。ForEach 键值必须为每个列表项提供唯一键值否则可能导致列表更新异常。本项目使用item.id?.toString()作为键值。4. swipeAction滑动删除4.1 滑动操作配置.swipeAction({end:{builder:(){this.itemEnd(index);},actionAreaDistance:40,}})end向左滑动后显示的操作区域尾部。builder滑动操作区域的 UI 构建器。actionAreaDistance: 40滑动操作区域的宽度。4.2 删除按钮构建BuilderitemEnd(index:number){Row(){Button(删除).borderRadius(5).backgroundColor($r(app.color.app_primary)).onClick(async(){if(!this.arr[index].date){this.arr.splice(index,1)}else{this.todoDb.deleteTodo(this.arr[index].id??0).then((isDelete:boolean){if(isDelete){this.arr.splice(index,1)}})}if(this.arr.length0){this.isTodoEmptytrue}})}.margin({left:7})}删除逻辑说明判断数据来源如果item.date为空说明是示例数据未入库直接从数组移除。数据库中的数据先调用todoDb.deleteTodo()删除成功后再从数组移除。空列表检测删除后如果数组为空切换到空状态视图。5. CustomImageToggle完成状态切换CustomImageToggle({isOn:item.isCompleted,onToggleChange:(value){letcurrentBonusPreferencesUtil.getNumberSync(AgentConstant.SP_BONUS,0)if(!valuecurrentBonus0){item.isCompletedtrue;return;}item.isCompletedvalue;PreferencesUtil.putSync(AgentConstant.SP_BONUS,value?currentBonus1:currentBonus-1)this.todoDb.updateTodo(item.id!!,item)this.finishVisiblethis.arr.every(itemitem.isCompletedtrue);}});完成状态切换涉及积分bonus系统获取当前积分从PreferencesUtil读取SP_BONUS。取消完成的保护逻辑如果积分为 0不允许取消完成状态防止积分变为负数。更新积分完成 1取消完成 -1通过PreferencesUtil.putSync持久化。更新数据库调用todoDb.updateTodo()保存完成状态。全完成检测如果所有待办都已完成显示庆祝弹窗finishVisible true。6. 添加待办半模态弹窗.bindSheet($$this.isShowTask,this.todoAddBuilder(),{blurStyle:BlurStyle.Thick,dragBar:true,title:this.DialogTitle(今日任务,添加,async(){if(StrUtil.isEmpty(this.value)){ToastUtil.showToast(任务内容不能为空)return}if(StrUtil.isEmpty(this.startTime)){ToastUtil.showToast(开始时间不能为空)return}if(StrUtil.isEmpty(this.stopTime)){ToastUtil.showToast(结束时间不能为空)return}constnewTodo:TodoItem{date:DateUtil.getTodayStr(yyyy-MM-dd),dayOfWeek:周DateUtil.getWeekDay(DateUtil.getTodayStr(HH-mm-ss)),time:${this.startTime}-${this.stopTime},content:this.value,isCompleted:false};consttodoIdawaitthis.todoDb.addTodo(newTodo);PreferencesUtil.putSync(isFirst,false);this.arr[]this.arrawaitthis.todoDb.getAllTodos();this.startTimethis.stopTimethis.isShowTaskfalsethis.isTodoEmptyfalse}),height:this.sheetHeight,keyboardAvoidMode:SheetKeyboardAvoidMode.TRANSLATE_AND_RESIZE})**半模态bindSheet**关键点$$this.isShowTask双向绑定显示状态$$语法允许框架内部修改该值如用户下滑关闭。blurStyle: BlurStyle.Thick背景毛玻璃效果。dragBar: true显示拖拽条用户可下滑关闭。keyboardAvoidMode键盘弹出时的避让模式。添加待办的表单包含任务名称、开始时间和结束时间通过IBestField和IBestCell组件实现输入和时间选择。6.1 刷新数据的方式this.arr[]this.arrawaitthis.todoDb.getAllTodos();先清空数组再重新赋值确保State能检测到变化并触发 UI 刷新。直接使用this.arr await ...赋值也是可行的但清空后赋值的模式在某些场景下更可靠。7. 功能入口IconText 组件Flex({justifyContent:FlexAlign.SpaceBetween}){IconText({icon:$r(app.media.ic_func_stroke),txt:每日诗词,fontColor:$r(app.color.color_function_txt_green),bgColor:$r(app.color.color_function_green),onFuncClick:async(){this.userIdUserInfoManager.getUserInfo()?.unionID??if(StrUtil.isBlank(this.userId)){this.login()return}awaitthis.getPoem();if(this.poemInfo){this.pageContext.openPage({param:this.poemInfo,routerName:PoemPage,onReturn:async(data){this.getPoem()}},true);}}}).width(45%)IconText({icon:$r(app.media.ic_func_speak),txt:练字字帖,fontColor:$r(app.color.color_function_txt_purple),bgColor:$r(app.color.color_function_blue),onFuncClick:(){this.pageContext.openPage({routerName:CopyPage},true);}}).width(45%)}.width(100%)IconText是项目封装的图标文字入口组件每个功能入口有独立的配色方案。点击「每日诗词」时需要登录验证而「练字字帖」则直接跳转。8. TimerComponent番茄钟计时器TimerComponent({totalTime:this.settingTime,onTimeUp:this.onTimerFinished,onTimeChange:this.onTimeChanged,controller:this.timerController}).onClick((){showDialogTime({titleBarAttribute:{titleText:请选择计时时间},timeAttribute:{timeType:TimeDialogType.HMS,startHours:0,startMinutes:0,startSeconds:0,selectTime:00-05-00,endHours:23,endMinutes:59,endSeconds:59,},timeConfirmClick:(date){this.settingTime(Number(date.getHours())||0)*3600(Number(date.getMinutes())||0)*60(Number(date.getSeconds())||0)},})})计时器默认 25 分钟番茄钟点击后弹出时间选择对话框用户可自定义时长。计时结束时触发onTimerFinished播放提醒铃声。9. 诗词阅读与字体注册9.1 楷体字体注册font.registerFont({familyName:kaiti,familySrc:$rawfile(font/kaiti.ttf)})通过font.registerFont注册自定义字体文件注册后可在Text组件中使用.fontFamily(kaiti)指定字体。9.2 诗词显示与朗读Column({space:12}){Text(this.poem).fontFamily(kaiti).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).lineSpacing(LengthMetrics.fp(18))}.mainCardStyle().onClick((){PoemReader.play(this.poem);}).fontFamily(kaiti)使用注册的楷体字体营造传统文化氛围。PoemReader.play点击诗词卡片触发朗读基于 TTS文本转语音能力。10. Scroll 容器配置Scroll(this.scroller){Column({space:10}){// 标题行// 倒计时 诗词// 功能入口// 待办列表}}.height(100%).width(100%).friction(0.6).align(Alignment.Top).scrollBar(BarState.Off).edgeEffect(EdgeEffect.Spring)属性说明.friction(0.6)滚动摩擦系数小于 1 更灵敏.scrollBar(BarState.Off)隐藏滚动条.edgeEffect(EdgeEffect.Spring)边界回弹效果提升手感11. 总结知识点说明ListForEach可滚动列表渲染ForEach 需提供唯一键值swipeAction滑动操作end配置左滑删除CustomImageToggle自定义开关组件处理完成状态与积分联动bindSheet半模态弹窗$$双向绑定显示状态PreferencesUtil轻量偏好存储保存积分和首次启动标记font.registerFont注册自定义字体$rawfile引用 rawfile 资源TimerComponent番茄钟计时器时间选择对话框集成PoemReader.playTTS 诗词朗读IconText功能入口组件带图标、文字和配色ScrollEdgeEffect.Spring弹性滚动效果空状态切换条件渲染空列表 vs 数据列表数据刷新模式清空数组 → 重新查询赋值确保 State 触发更新待办列表看似是一个简单的 CRUD 页面实则涉及列表渲染、手势交互、状态管理、数据持久化、自定义字体、计时器、TTS 朗读等多个技术点的综合运用。理解这些模式的组合方式是构建复杂交互页面的基础。

相关新闻

AI编程智能体实战:从LangChain到商业级Agent开发全解析

AI编程智能体实战:从LangChain到商业级Agent开发全解析

如果你正在学习AI编程智能体,却感觉知识碎片化、难以系统掌握,这篇文章正是为你准备的。很多开发者尝试过LangChain、Cursor等工具,但往往停留在基础功能使用,无法真正构建出商业级的编程Agent。问题的核心不在于工具本身&#xf…

2026/7/13 23:58:17阅读更多 →
Pandas数据清洗七把瑞士军刀:assign、pipe、loc等核心函数实战指南

Pandas数据清洗七把瑞士军刀:assign、pipe、loc等核心函数实战指南

1. 项目概述:这不是一份“函数清单”,而是一本数据清洗实战手记“Pandas Playbook: 7 Must-Know Comprehensive Data Functions”——这个标题乍看像是一份泛泛而谈的函数罗列教程,但在我过去十年带团队做金融风控建模、电商用户行为分析和制…

2026/7/13 23:58:17阅读更多 →
2028 年 1 月起索尼停售 PS 实体光盘,数字下载占比 78%成主因

2028 年 1 月起索尼停售 PS 实体光盘,数字下载占比 78%成主因

索尼宣布 2028 年起停售 PS 实体游戏光盘今日,索尼宣布自 2028 年 1 月起,将不再生产 PlayStation 游戏的实体光盘。届时,新游戏仅以数字格式在 PlayStation 商店和零售商处提供。此消息引发了部分玩家对游戏所有权未来走向的担忧。顺应消费趋…

2026/7/13 23:58:17阅读更多 →
Gemini 图片代码怎么用,搭配 AI 导出鸭梳理技术方案,实测多类文档导出选型差异

Gemini 图片代码怎么用,搭配 AI 导出鸭梳理技术方案,实测多类文档导出选型差异

引言 随着Gemini图文生成功能普及,大量用户在调取图片配套代码后,面临代码落地、文档批量导出困难等问题,从个人创作者到中小企业办公人员,都急需低成本、高效率的导出工具与落地方法,AI 导出鸭依托全平台适配优势成为…

2026/7/14 6:54:15阅读更多 →
LangChain - AI应用开发利器(八)

LangChain - AI应用开发利器(八)

1. 核心组件 - 少样本提示(few-shotting) 1.1. 概念 少样本提⽰是⼀种通过向 LLM 提供少量具体⽰例或样本,来教会它如何执⾏某项特定任务的技术。提⾼模型性能的最有效⽅法之⼀是给出⼀个【模型⽰例】指导⼤模型你想做什么、怎么做。下⾯⽤⼀…

2026/7/14 6:54:15阅读更多 →
新能源汽车:3大标定工具横向扒底|CANape/TSMaster/ZXDoc怎么选?A2L查看实操+市场份额全解析

新能源汽车:3大标定工具横向扒底|CANape/TSMaster/ZXDoc怎么选?A2L查看实操+市场份额全解析

新能源汽车:3大标定工具横向扒底|CANape/TSMaster/ZXDoc怎么选?A2L查看实操市场份额全解析 几十万VS几千块!新能源标定三巨头CANape/TSMaster/ZXDoc实测对比,A2L查看一步到位标签:#新能源三电 #ECU标定 #A2…

2026/7/14 6:54:15阅读更多 →
利用TensorFlow Datasets高效加载与预处理石头剪刀布图像数据

利用TensorFlow Datasets高效加载与预处理石头剪刀布图像数据

1. TensorFlow Datasets简介与石头剪刀布数据集TensorFlow Datasets(TFDS)是TensorFlow官方提供的数据集工具库,它就像是一个精心整理的工具箱,帮你把常见机器学习数据集打包成即拆即用的格式。我刚开始接触计算机视觉项目时&…

2026/7/14 6:54:15阅读更多 →
国产USB转千兆网卡芯片CH398 VLAN应用

国产USB转千兆网卡芯片CH398 VLAN应用

CH398 VLAN 功能验证与应用 CH398 是沁恒微电子推出的 USB 3.0 转千兆以太网控制器,内置自研 RISC-V 处理器,集成 USB3.2 Gen1 及 10/100/1000M 自适应以太网 PHY,面向工控、嵌入式、扩展坞等需要稳定高速网络接入的场景。本文验证 CH398 的…

2026/7/14 6:54:15阅读更多 →
Python舞台视频分析:人脸识别与动作同步技术详解

Python舞台视频分析:人脸识别与动作同步技术详解

最近在整理经典舞台表演时,重新观看了少女时代在江苏卫视演唱的《Lion Heart》,这个舞台确实展现了顶级女团的实力。作为技术博主,我发现很多开发者对视频处理、音频分析和舞台效果实现很感兴趣,今天就从技术角度拆解如何用Python…

2026/7/14 6:49:06阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/14 4:56:14阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/14 2:55:05阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/14 6:17:41阅读更多 →
【Cursor数据库安全红线】:自动执行SQL前必须校验的6项权限策略,金融级项目已强制落地

【Cursor数据库安全红线】:自动执行SQL前必须校验的6项权限策略,金融级项目已强制落地

更多请点击: https://intelliparadigm.com 第一章:Cursor数据库安全红线概览 Cursor 作为一款基于 AI 的智能编程助手,其本地数据库(SQLite 存储)承载着用户代码片段、会话历史、自定义规则及敏感上下文信息。理解其安…

2026/7/14 0:03:18阅读更多 →
【Notion AI写作避坑白皮书】:基于127份真实用户失败案例,总结6大致命误用陷阱

【Notion AI写作避坑白皮书】:基于127份真实用户失败案例,总结6大致命误用陷阱

更多请点击: https://codechina.net 第一章:Notion AI写作辅助的底层能力边界认知 Notion AI 并非通用大语言模型的直接封装,而是基于 Llama 系列与自研微调模型构建的轻量化推理服务,其输入上下文窗口严格限制在 8192 token&…

2026/7/14 0:03:18阅读更多 →
AI Agent数据越界行为如何被精准溯源?——基于GDPR/CCPA双合规的5层审计框架实战指南

AI Agent数据越界行为如何被精准溯源?——基于GDPR/CCPA双合规的5层审计框架实战指南

更多请点击: https://kaifayun.com 第一章:AI Agent数据越界行为的合规性挑战与溯源必要性 AI Agent在自主执行任务过程中,可能因提示注入、上下文污染或权限配置缺陷,无意或有意访问、缓存、传输受保护数据(如PII、G…

2026/7/14 0:03:18阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/13 4:21:17阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/14 4:45:36阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/14 2:42:17阅读更多 →