零基础学 ArkUI17手把手教你开发一个备忘录 App 应用场景备忘录是我们手机里最常用的工具之一——“明天 10 点开会记得带材料”“记得买牛奶”“郭哥的生日 3 月 15 号”……我们要开发的备忘录 App 会实现创建笔记标题 内容 时间戳查看笔记列表卡片式展示按时间排序编辑已有笔记滑动删除笔记搜索笔记按标题 / 内容关键字数据本地持久化关闭 App 不丢失⚙️ 运行环境要求项目版本要求操作系统Windows 10/11、macOS 13 或 Ubuntu 22.04DevEco Studio5.0.3.800 及以上HarmonyOS SDKAPI 12HarmonyOS 5.0.0及以上应用模型Stage 模型开发语言ArkTS环境配置截图示意️ 实战从零搭建备忘录Step 1理解「数据驱动 UI」的编程思维写备忘录应用之前我们先要建立两个关键认知1. 状态驱动视图数据State → UI 渲染 ← 用户操作触发数据变更 ↑ | └──────────────── 自动刷新 ────────────┘你修改数据UI 自动变——你不用去操作 DOM 或视图对象。2. 本地持久化内存数据在 App 关闭后会消失所以我们需要把笔记存到磁盘上。HarmonyOS 提供了preferences首选项API 来存储键值对数据——对简单的文本类数据非常方便。Step 2项目结构com.example.notepad/ ├── entry/src/main/ets/ │ ├── entryability/ │ │ └── EntryAbility.ts │ ├── pages/ │ │ └── Index.ets ← 主页面所有逻辑都在这里 │ └── common/ │ └── NoteModel.ets ← 笔记数据模型推荐拆出来Step 3定义笔记数据模型新建common/NoteModel.ets// common/NoteModel.ets — 笔记的数据模型exportclassNote{id:string;title:string;content:string;createTime:string;// ISO 格式时间戳如 2025-03-15 10:30:00isFavorite:boolean;constructor(title:string,content:string){this.idDate.now().toString();// 用时间戳作为唯一 IDthis.titletitle;this.contentcontent;this.createTimethis.getNowStr();this.isFavoritefalse;}getNowStr():string{constdnewDate();constpad(n:number)n.toString().padStart(2,0);return${d.getFullYear()}-${pad(d.getMonth()1)}-${pad(d.getDate())}${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())};}}为什么用 Date.now() 做 ID简单且保证唯一性——同一毫秒不会创建两个笔记。正式项目建议用 UUID。Step 4编写主页面 — 从布局开始打开pages/Index.ets我们先写出整体的页面骨架// pages/Index.ets — 备忘录主页面import{Note}from../common/NoteModel;// 数据持久化工具importpreferencesfromohos.data.preferences;EntryComponentstruct NoteApp{// 状态变量 Statenotes:Note[][];// 所有笔记StatesearchText:string;// 搜索关键字StateshowCreate:booleanfalse;// 是否显示新建面板StatecurrentNote:Note|nullnull;// 当前编辑的笔记// 新建笔记的临时数据StateeditTitle:string;StateeditContent:string;privatepref!:preferences.Preferences;// 持久化对象// 生命周期 aboutToAppear(){this.loadData();}asyncloadData(){// 获取持久化实例constcontextgetContext(this);this.prefawaitpreferences.getPreferences(context,note_store);// 读取已保存的笔记 JSON 字符串constjsonthis.pref.get(notes,[]);constarr:any[]JSON.parse(json);this.notesarr.map((item:any)Object.assign(newNote(,),item));}asyncsaveData(){awaitthis.pref.put(notes,JSON.stringify(this.notes));awaitthis.pref.flush();}// 添加 / 更新笔记asynchandleSave(){if(!this.editTitle.trim()){return;// 标题不能为空}if(this.currentNote){// 编辑模式更新已有笔记this.currentNote.titlethis.editTitle;this.currentNote.contentthis.editContent;}else{// 新建模式constnotenewNote(this.editTitle,this.editContent);this.notes.unshift(note);// 新笔记插到最前面}awaitthis.saveData();// 重置编辑状态this.showCreatefalse;this.currentNotenull;this.editTitle;this.editContent;}// 编辑笔记startEdit(note:Note){this.currentNotenote;this.editTitlenote.title;this.editContentnote.content;this.showCreatetrue;}// 删除笔记asyncdeleteNote(note:Note){constidxthis.notes.indexOf(note);if(idx-1){this.notes.splice(idx,1);awaitthis.saveData();}}// 切换收藏asynctoggleFavorite(note:Note){note.isFavorite!note.isFavorite;awaitthis.saveData();}// 计算属性搜索过滤后的笔记 getfilteredNotes():Note[]{if(!this.searchText.trim()){returnthis.notes;}constkwthis.searchText.toLowerCase();returnthis.notes.filter(nn.title.toLowerCase().includes(kw)||n.content.toLowerCase().includes(kw));}// UI 构建 build(){Column(){// ---- 顶部标题栏 ----Row(){Text( 备忘录).fontSize(24).fontWeight(FontWeight.Bold).layoutWeight(1)Button({type:ButtonType.Circle}){Image($r(app.media.ic_add)).width(24).height(24)}.width(44).height(44).backgroundColor(#007AFF).onClick((){this.currentNotenull;this.editTitle;this.editContent;this.showCreatetrue;})}.width(100%).padding({top:12,bottom:8,left:16,right:16})// ---- 搜索框 ----TextInput({placeholder: 搜索笔记...,text:this.searchText}).width(92%).height(40).backgroundColor(#F0F0F0).borderRadius(20).padding({left:16}).onChange((val:string){this.searchTextval;})// ---- 笔记列表 OR 空状态 ----if(this.filteredNotes.length0){Column(){Text( 还没有笔记).fontSize(18).fontColor(#999)Text(点击右上角 创建你的第一条笔记).fontSize(14).fontColor(#bbb).margin({top:8})}.layoutWeight(1).justifyContent(FlexAlign.Center)}else{List(){ForEach(this.filteredNotes,(note:Note){ListItem(){this.NoteCard({note:note})}.swipeAction({end:this.DeleteButton(note)})},(note:Note)note.id)}.layoutWeight(1).width(100%)}}.width(100%).height(100%).backgroundColor(#F2F2F7)// ---- 新建/编辑弹窗 ----.bindSheet(this.showCreate,this.CreateSheet())}// Builder可复用的 UI 片段 BuilderNoteCard({note}:{note:Note}){Column(){Row(){Text(note.title).fontSize(17).fontWeight(FontWeight.Bold).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1).layoutWeight(1)Text(note.createTime).fontSize(12).fontColor(#999)}.width(100%)if(note.content){Text(note.content).fontSize(15).fontColor(#555).lineHeight(22).maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis}).width(100%).margin({top:6})}}.width(92%).padding(16).backgroundColor(#FFFFFF).borderRadius(12).margin({top:8}).shadow({radius:4,color:#20000000,offsetX:0,offsetY:2}).onClick((){this.startEdit(note);})}BuilderDeleteButton(note:Note){Button(删除).backgroundColor(#FF3B30).fontColor(#fff).borderRadius(8).width(80).height(80%).onClick((){this.deleteNote(note);})}BuilderCreateSheet(){Column(){Text(this.currentNote?编辑笔记:新建笔记).fontSize(20).fontWeight(FontWeight.Bold).margin({bottom:16})TextInput({placeholder:笔记标题,text:this.editTitle}).width(100%).height(44).backgroundColor(#F8F8F8).borderRadius(8).padding({left:12}).onChange((val:string){this.editTitleval;})TextArea({placeholder:开始记录...,text:this.editContent}).width(100%).height(200).backgroundColor(#F8F8F8).borderRadius(8).padding(12).margin({top:12}).onChange((val:string){this.editContentval;})Row(){Button(取消).backgroundColor(#E5E5EA).fontColor(#333).borderRadius(8).width(45%).onClick((){this.showCreatefalse;this.currentNotenull;})Button(保存).backgroundColor(#007AFF).fontColor(#fff).borderRadius(8).width(45%).onClick((){this.handleSave();})}.width(100%).justifyContent(FlexAlign.SpaceBetween).margin({top:20})}.padding(24).width(100%)}} 核心知识点深度解析1.State— 状态驱动的响应式编程State是 ArkUI 响应式系统的基石。当State变量变化时框架自动重新渲染依赖该变量的 UI 部分。Statenotes:Note[][];// 当你执行 this.notes.push(newNote) 时List 自动刷新原理ArkUI 在编译阶段对State变量建立依赖图。渲染时记录哪些组件读取了该状态状态变更时只重绘相关组件——不是全量刷新。2.Builder— 复用 UI 片段Builder让你把一段 UI 封装成函数避免重复代码BuilderNoteCard({note}:{note:Note}){// ... 一张笔记卡片的 UI 定义}// 在 ListItem 中引用ListItem(){this.NoteCard({note:note})}3.bindSheet— 底部弹窗bindSheet是 ArkUI 提供的底部弹出面板组件非常适合新建 / 编辑表单.bindSheet(this.showCreate,this.CreateSheet())// 第一个参数是 bool 控制显示/隐藏// 第二个参数是 Builder 定义的面板内容4. JSON 序列化与持久化// 存把对象数组转成 JSON 字符串awaitpref.put(notes,JSON.stringify(this.notes));// 取把 JSON 字符串解析回对象数组constjsonpref.get(notes,[]);constarrJSON.parse(json);// 注意JSON.parse 不会自动调用 constructor需手动恢复原型this.notesarr.map(itemObject.assign(newNote(,),item));⚠️ 避坑指南坑原因正确做法笔记数据 App 重启丢失只存在内存中必须用preferences或数据库持久化JSON.parse 后方法丢失JSON 只管数据不管原型链用Object.assign(new Note(), raw)恢复ForEach 循环不刷新缺少key属性ForEach(arr, fn, item item.id)写第三个参数TextArea 文本换行不对忘了设置maxLines或lineHeight显式设置lineHeight(22)和maxLines(N)编辑时原数据被改直接修改了this.notes中的对象深拷贝一份再编辑或直接用对象引用简单场景 最佳实践尽早持久化每次增删改后马上调用saveData()不要等用户退出搜索防抖优化高频输入时全文搜索可能卡顿简单场景用onChange实时过滤即可卡片圆角 阴影borderRadius(12) shadow()让列表更有质感空状态引导不要只显示白屏——空状态提示 按钮引导用户创建第一条笔记类型安全用export class Note而非any[]IDE 能给你更好的代码补全异步初始化aboutToAppear中不要用sync操作所有 IO 都用async/await 扩展挑战学有余力的同学可以试试以下进阶功能富文本编辑支持加粗、列表、图片插入使用RichEditor组件标签分类给笔记打标签按标签过滤State tags: string[]暗黑模式适配使用Styles定义主题变量根据系统主题切换数据导出支持导出为 TXT / Markdown 文件使用fileIoAPI搜索高亮搜索结果中匹配的关键字用不同颜色显示运行结果完整截图官方文档HarmonyOS 应用开发文档开发者社区华为开发者论坛欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net/
ArkUI开发备忘录17
发布时间:2026/6/8 22:47:06
零基础学 ArkUI17手把手教你开发一个备忘录 App 应用场景备忘录是我们手机里最常用的工具之一——“明天 10 点开会记得带材料”“记得买牛奶”“郭哥的生日 3 月 15 号”……我们要开发的备忘录 App 会实现创建笔记标题 内容 时间戳查看笔记列表卡片式展示按时间排序编辑已有笔记滑动删除笔记搜索笔记按标题 / 内容关键字数据本地持久化关闭 App 不丢失⚙️ 运行环境要求项目版本要求操作系统Windows 10/11、macOS 13 或 Ubuntu 22.04DevEco Studio5.0.3.800 及以上HarmonyOS SDKAPI 12HarmonyOS 5.0.0及以上应用模型Stage 模型开发语言ArkTS环境配置截图示意️ 实战从零搭建备忘录Step 1理解「数据驱动 UI」的编程思维写备忘录应用之前我们先要建立两个关键认知1. 状态驱动视图数据State → UI 渲染 ← 用户操作触发数据变更 ↑ | └──────────────── 自动刷新 ────────────┘你修改数据UI 自动变——你不用去操作 DOM 或视图对象。2. 本地持久化内存数据在 App 关闭后会消失所以我们需要把笔记存到磁盘上。HarmonyOS 提供了preferences首选项API 来存储键值对数据——对简单的文本类数据非常方便。Step 2项目结构com.example.notepad/ ├── entry/src/main/ets/ │ ├── entryability/ │ │ └── EntryAbility.ts │ ├── pages/ │ │ └── Index.ets ← 主页面所有逻辑都在这里 │ └── common/ │ └── NoteModel.ets ← 笔记数据模型推荐拆出来Step 3定义笔记数据模型新建common/NoteModel.ets// common/NoteModel.ets — 笔记的数据模型exportclassNote{id:string;title:string;content:string;createTime:string;// ISO 格式时间戳如 2025-03-15 10:30:00isFavorite:boolean;constructor(title:string,content:string){this.idDate.now().toString();// 用时间戳作为唯一 IDthis.titletitle;this.contentcontent;this.createTimethis.getNowStr();this.isFavoritefalse;}getNowStr():string{constdnewDate();constpad(n:number)n.toString().padStart(2,0);return${d.getFullYear()}-${pad(d.getMonth()1)}-${pad(d.getDate())}${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())};}}为什么用 Date.now() 做 ID简单且保证唯一性——同一毫秒不会创建两个笔记。正式项目建议用 UUID。Step 4编写主页面 — 从布局开始打开pages/Index.ets我们先写出整体的页面骨架// pages/Index.ets — 备忘录主页面import{Note}from../common/NoteModel;// 数据持久化工具importpreferencesfromohos.data.preferences;EntryComponentstruct NoteApp{// 状态变量 Statenotes:Note[][];// 所有笔记StatesearchText:string;// 搜索关键字StateshowCreate:booleanfalse;// 是否显示新建面板StatecurrentNote:Note|nullnull;// 当前编辑的笔记// 新建笔记的临时数据StateeditTitle:string;StateeditContent:string;privatepref!:preferences.Preferences;// 持久化对象// 生命周期 aboutToAppear(){this.loadData();}asyncloadData(){// 获取持久化实例constcontextgetContext(this);this.prefawaitpreferences.getPreferences(context,note_store);// 读取已保存的笔记 JSON 字符串constjsonthis.pref.get(notes,[]);constarr:any[]JSON.parse(json);this.notesarr.map((item:any)Object.assign(newNote(,),item));}asyncsaveData(){awaitthis.pref.put(notes,JSON.stringify(this.notes));awaitthis.pref.flush();}// 添加 / 更新笔记asynchandleSave(){if(!this.editTitle.trim()){return;// 标题不能为空}if(this.currentNote){// 编辑模式更新已有笔记this.currentNote.titlethis.editTitle;this.currentNote.contentthis.editContent;}else{// 新建模式constnotenewNote(this.editTitle,this.editContent);this.notes.unshift(note);// 新笔记插到最前面}awaitthis.saveData();// 重置编辑状态this.showCreatefalse;this.currentNotenull;this.editTitle;this.editContent;}// 编辑笔记startEdit(note:Note){this.currentNotenote;this.editTitlenote.title;this.editContentnote.content;this.showCreatetrue;}// 删除笔记asyncdeleteNote(note:Note){constidxthis.notes.indexOf(note);if(idx-1){this.notes.splice(idx,1);awaitthis.saveData();}}// 切换收藏asynctoggleFavorite(note:Note){note.isFavorite!note.isFavorite;awaitthis.saveData();}// 计算属性搜索过滤后的笔记 getfilteredNotes():Note[]{if(!this.searchText.trim()){returnthis.notes;}constkwthis.searchText.toLowerCase();returnthis.notes.filter(nn.title.toLowerCase().includes(kw)||n.content.toLowerCase().includes(kw));}// UI 构建 build(){Column(){// ---- 顶部标题栏 ----Row(){Text( 备忘录).fontSize(24).fontWeight(FontWeight.Bold).layoutWeight(1)Button({type:ButtonType.Circle}){Image($r(app.media.ic_add)).width(24).height(24)}.width(44).height(44).backgroundColor(#007AFF).onClick((){this.currentNotenull;this.editTitle;this.editContent;this.showCreatetrue;})}.width(100%).padding({top:12,bottom:8,left:16,right:16})// ---- 搜索框 ----TextInput({placeholder: 搜索笔记...,text:this.searchText}).width(92%).height(40).backgroundColor(#F0F0F0).borderRadius(20).padding({left:16}).onChange((val:string){this.searchTextval;})// ---- 笔记列表 OR 空状态 ----if(this.filteredNotes.length0){Column(){Text( 还没有笔记).fontSize(18).fontColor(#999)Text(点击右上角 创建你的第一条笔记).fontSize(14).fontColor(#bbb).margin({top:8})}.layoutWeight(1).justifyContent(FlexAlign.Center)}else{List(){ForEach(this.filteredNotes,(note:Note){ListItem(){this.NoteCard({note:note})}.swipeAction({end:this.DeleteButton(note)})},(note:Note)note.id)}.layoutWeight(1).width(100%)}}.width(100%).height(100%).backgroundColor(#F2F2F7)// ---- 新建/编辑弹窗 ----.bindSheet(this.showCreate,this.CreateSheet())}// Builder可复用的 UI 片段 BuilderNoteCard({note}:{note:Note}){Column(){Row(){Text(note.title).fontSize(17).fontWeight(FontWeight.Bold).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1).layoutWeight(1)Text(note.createTime).fontSize(12).fontColor(#999)}.width(100%)if(note.content){Text(note.content).fontSize(15).fontColor(#555).lineHeight(22).maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis}).width(100%).margin({top:6})}}.width(92%).padding(16).backgroundColor(#FFFFFF).borderRadius(12).margin({top:8}).shadow({radius:4,color:#20000000,offsetX:0,offsetY:2}).onClick((){this.startEdit(note);})}BuilderDeleteButton(note:Note){Button(删除).backgroundColor(#FF3B30).fontColor(#fff).borderRadius(8).width(80).height(80%).onClick((){this.deleteNote(note);})}BuilderCreateSheet(){Column(){Text(this.currentNote?编辑笔记:新建笔记).fontSize(20).fontWeight(FontWeight.Bold).margin({bottom:16})TextInput({placeholder:笔记标题,text:this.editTitle}).width(100%).height(44).backgroundColor(#F8F8F8).borderRadius(8).padding({left:12}).onChange((val:string){this.editTitleval;})TextArea({placeholder:开始记录...,text:this.editContent}).width(100%).height(200).backgroundColor(#F8F8F8).borderRadius(8).padding(12).margin({top:12}).onChange((val:string){this.editContentval;})Row(){Button(取消).backgroundColor(#E5E5EA).fontColor(#333).borderRadius(8).width(45%).onClick((){this.showCreatefalse;this.currentNotenull;})Button(保存).backgroundColor(#007AFF).fontColor(#fff).borderRadius(8).width(45%).onClick((){this.handleSave();})}.width(100%).justifyContent(FlexAlign.SpaceBetween).margin({top:20})}.padding(24).width(100%)}} 核心知识点深度解析1.State— 状态驱动的响应式编程State是 ArkUI 响应式系统的基石。当State变量变化时框架自动重新渲染依赖该变量的 UI 部分。Statenotes:Note[][];// 当你执行 this.notes.push(newNote) 时List 自动刷新原理ArkUI 在编译阶段对State变量建立依赖图。渲染时记录哪些组件读取了该状态状态变更时只重绘相关组件——不是全量刷新。2.Builder— 复用 UI 片段Builder让你把一段 UI 封装成函数避免重复代码BuilderNoteCard({note}:{note:Note}){// ... 一张笔记卡片的 UI 定义}// 在 ListItem 中引用ListItem(){this.NoteCard({note:note})}3.bindSheet— 底部弹窗bindSheet是 ArkUI 提供的底部弹出面板组件非常适合新建 / 编辑表单.bindSheet(this.showCreate,this.CreateSheet())// 第一个参数是 bool 控制显示/隐藏// 第二个参数是 Builder 定义的面板内容4. JSON 序列化与持久化// 存把对象数组转成 JSON 字符串awaitpref.put(notes,JSON.stringify(this.notes));// 取把 JSON 字符串解析回对象数组constjsonpref.get(notes,[]);constarrJSON.parse(json);// 注意JSON.parse 不会自动调用 constructor需手动恢复原型this.notesarr.map(itemObject.assign(newNote(,),item));⚠️ 避坑指南坑原因正确做法笔记数据 App 重启丢失只存在内存中必须用preferences或数据库持久化JSON.parse 后方法丢失JSON 只管数据不管原型链用Object.assign(new Note(), raw)恢复ForEach 循环不刷新缺少key属性ForEach(arr, fn, item item.id)写第三个参数TextArea 文本换行不对忘了设置maxLines或lineHeight显式设置lineHeight(22)和maxLines(N)编辑时原数据被改直接修改了this.notes中的对象深拷贝一份再编辑或直接用对象引用简单场景 最佳实践尽早持久化每次增删改后马上调用saveData()不要等用户退出搜索防抖优化高频输入时全文搜索可能卡顿简单场景用onChange实时过滤即可卡片圆角 阴影borderRadius(12) shadow()让列表更有质感空状态引导不要只显示白屏——空状态提示 按钮引导用户创建第一条笔记类型安全用export class Note而非any[]IDE 能给你更好的代码补全异步初始化aboutToAppear中不要用sync操作所有 IO 都用async/await 扩展挑战学有余力的同学可以试试以下进阶功能富文本编辑支持加粗、列表、图片插入使用RichEditor组件标签分类给笔记打标签按标签过滤State tags: string[]暗黑模式适配使用Styles定义主题变量根据系统主题切换数据导出支持导出为 TXT / Markdown 文件使用fileIoAPI搜索高亮搜索结果中匹配的关键字用不同颜色显示运行结果完整截图官方文档HarmonyOS 应用开发文档开发者社区华为开发者论坛欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net/