当前位置: 首页 > news >正文

鸿蒙原生应用从0到1:备忘录模块 —— 多视图切换与搜索实战

鸿蒙原生应用从0到1:备忘录模块 —— 多视图切换与搜索实战

系列第四篇,深入「备忘录」页面开发,重点讲解分类筛选 + 关键词搜索、详情视图、编辑模式、多视图切换等核心功能。


一、功能概览

备忘录是生活助手 App 中功能最丰富的页面,因为它需要在有限空间内承载多种视图状态:列表浏览、详情查看、新增编辑。

┌─────────────────────────────────┐ │ ← 返回 备忘录 共6篇 │ ├─────────────────────────────────┤ │ 🔍 搜索笔记... │ ← 搜索栏 ├─────────────────────────────────┤ │ [全部] [工作] [学习] [生活] ... │ ← 分类标签 ├─────────────────────────────────┤ │ ┌─────────────────────────────┐ │ │ │ 工作 2025-01-15 │ │ │ │ 项目管理要点 │ │ │ │ 1. 明确项目目标和范围... │ │ ← 笔记卡片 │ │ 查看全文 → │ │ │ └─────────────────────────────┘ │ │ ┌─────────────────────────────┐ │ │ │ 学习 2025-01-14 │ │ │ │ ArkTS学习笔记 │ │ │ │ ArkTS是鸿蒙原生开发语言... │ │ │ │ 查看全文 → │ │ │ └─────────────────────────────┘ │ ├─────────────────────────────────┤ │ [+ 写笔记] │ └─────────────────────────────────┘

二、数据模型

interfaceNoteItem{id:number;// 唯一标识title:string;// 标题content:string;// 内容(支持多行)category:string;// 分类date:string;// 日期}interfaceNoteCategory{name:string;// 筛选标识label:string;// 显示文字color:string;// 颜色}

与待办页面不同,笔记的content多行文本,内容可能较长,因此在列表显示时需要做截断处理。


三、复杂状态管理

备忘录页面有多种视图状态,需要 5 个状态变量来协调:

@Componentstruct NotePage{@Statenotes:NoteItem[]=[];@StateactiveCategory:string='全部';@StateshowAddDialog:boolean=false;@StateshowDetail:boolean=false;// 详情视图@StatedetailNote:NoteItem|null=null;// 当前查看的笔记@StateeditingNote:NoteItem|null=null;// 编辑中的笔记@StatenewTitle:string='';@StatenewContent:string='';@StatenewCategory:string='生活';@StatesearchText:string='';}

3.1 视图状态转移

列表浏览 ──点击卡片──→ 详情查看 ──点击编辑──→ 编辑模式 ↑ │ │ └──── 返回列表 ──────┘ │ └──────────────── 保存/取消 ──────────────────┘ 列表浏览 ──点击 + ──→ 新增模式

3.2 核心状态管理原则

  1. showAddDialog控制弹窗显隐:新增和编辑共用同一个弹窗
  2. editingNote区分新增/编辑:为null表示新增,有值表示编辑
  3. showDetail+detailNote控制详情视图:两者配合使用

四、数据过滤——分类 + 搜索双过滤

这是本节最重要的知识点:

getfilteredNotes():NoteItem[]{letresult:NoteItem[]=this.notes;// 第一层过滤:分类if(this.activeCategory!=='全部'){result=result.filter((item:NoteItem)=>item.category===this.activeCategory);}// 第二层过滤:关键字搜索if(this.searchText.trim()){constkeyword:string=this.searchText.trim().toLowerCase();result=result.filter((item:NoteItem)=>item.title.toLowerCase().includes(keyword)||item.content.toLowerCase().includes(keyword));}returnresult;}

设计亮点

  1. 链式过滤:先分类后搜索,逻辑清晰
  2. 大小写无关toLowerCase()让搜索对大小写不敏感
  3. 标题+内容双字段匹配:用户可以在笔记全文搜索
  4. getter写法:使用get filteredNotes()而非方法,让模板中直接使用this.filteredNotes,调用更简洁

五、UI 构建详解

5.1 搜索栏

TextInput({placeholder:'搜索笔记...',text:this.searchText}).width('90%').height(40).borderRadius(20)// 圆角效果.backgroundColor($r('app.color.bg_card')).padding({left:16}).onChange((value:string)=>{this.searchText=value;})

细节

  • borderRadius(20)让搜索栏呈现胶囊形状
  • onChange回调实时更新searchText,实现即时搜索

5.2 笔记卡片

@BuildernoteCard(note:NoteItem):void{Column(){// 顶部:分类标签 + 日期Row(){Text(note.category).fontSize(10).fontColor(Color.White).padding({left:10,right:10,top:3,bottom:3}).backgroundColor(this.getCategoryColor(note.category)).borderRadius(8)Blank()Text(note.date).fontSize($r('app.float.tiny_font_size')).fontColor($r('app.color.text_secondary'))}.width('100%')// 标题Text(note.title).fontSize($r('app.float.body_font_size')).fontWeight(FontWeight.Medium).fontColor($r('app.color.text_primary')).width('100%').margin({top:8})// 内容预览(截断)Text(this.getPreviewText(note.content)).fontSize($r('app.float.small_font_size')).fontColor($r('app.color.text_secondary')).lineHeight(20).width('100%').margin({top:4})// 查看全文入口Row(){Blank()Text('查看全文 →').fontSize($r('app.float.tiny_font_size')).fontColor($r('app.color.primary'))}.width('100%').margin({top:8})}.width('100%').padding(16).backgroundColor($r('app.color.bg_card')).borderRadius($r('app.float.card_radius')).margin({bottom:10}).shadow({radius:4,color:$r('app.color.shadow'),offsetY:1}).onClick(()=>{this.viewDetail(note);})}

5.3 内容截断

getPreviewText(content:string):string{returncontent.length>40?content.substring(0,40)+'...':content;}

为什么截断:笔记内容可能很长,在列表卡片中完整显示会导致卡片高度不一致、信息密度降低。截断到 40 个字符并加...是移动端常见做法。


六、多视图切换——详情模式

6.1 视图切换逻辑

列表浏览和详情查看是两种完全不同的视图,我通过条件渲染实现切换:

// build() 中if(this.showDetail&&this.detailNote){// 详情视图this.detailView()}else{// 列表视图(包含搜索、分类、卡片列表)this.listView()}

6.2 详情视图

@BuilderdetailView():void{Column(){// 顶部操作栏Row(){Text('← 返回列表').onClick(()=>{this.showDetail=false;this.detailNote=null;})Blank()Text('✏️')// 编辑.onClick(()=>{this.startEdit(this.detailNote!);})Text('🗑️')// 删除.onClick(()=>{this.deleteNote(this.detailNote!.id);})}// 笔记标题Text(this.detailNote.title).fontSize($r('app.float.title_font_size')).fontWeight(FontWeight.Bold)// 元信息Row(){Text(this.detailNote.category).backgroundColor(this.getCategoryColor(this.detailNote.category))Text(this.detailNote.date)}// 正文内容Text(this.detailNote.content).fontSize($r('app.float.body_font_size')).lineHeight(26).width('100%')}.padding(20)}

6.3 进入详情

viewDetail(note:NoteItem):void{this.detailNote=note;this.showDetail=true;}

注意detailNote赋值为原始对象的引用。如果在详情页修改笔记内容,会影响原数组中的对象。这里因为实现了编辑功能后同步更新,所以没问题。如果只是只读详情,建议深拷贝。


七、编辑功能

7.1 进入编辑模式

startEdit(note:NoteItem):void{this.editingNote=note;this.newTitle=note.title;this.newContent=note.content;this.newCategory=note.category;this.showAddDialog=true;// 复用新增弹窗this.showDetail=false;// 关闭详情}

复用设计:新增和编辑共用同一个弹窗,通过editingNote区分:

  • editingNote === null→ 新增模式,按钮文字为"创建"
  • editingNote !== null→ 编辑模式,按钮文字为"保存修改"

7.2 保存编辑

updateNote():void{if(!this.editingNote||!this.newTitle.trim()){return;}constindex:number=this.notes.findIndex((item:NoteItem)=>item.id===this.editingNote!.id);if(index!==-1){constupdatedNote:NoteItem={id:this.notes[index].id,// 保持 ID 不变title:this.newTitle.trim(),content:this.newContent.trim(),category:this.newCategory,date:this.notes[index].date// 保留原日期};this.notes[index]=updatedNote;this.notes=this.notes.slice();// 触发 UI 刷新this.detailNote=this.notes[index];// 同步更新详情视图}this.resetForm();this.editingNote=null;this.showAddDialog=false;}

7.3 新增与编辑的按钮逻辑

Button(this.editingNote?'保存修改':'创建').onClick(()=>{if(this.editingNote){this.updateNote();}else{this.addNote();}})

八、CRUD 完整流程

8.1 Create —— 新增

addNote():void{if(!this.newTitle.trim())return;constnewId:number=this.notes.length>0?this.notes[this.notes.length-1].id+1:1;constdateStr:string=this.getTodayString();constnewNote:NoteItem={id:newId,title:this.newTitle.trim(),content:this.newContent.trim(),category:this.newCategory,date:dateStr};this.notes.push(newNote);this.resetForm();this.showAddDialog=false;}

8.2 Delete —— 删除

deleteNote(id:number):void{this.notes=this.notes.filter((item:NoteItem)=>item.id!==id);// 如果当前在详情页且删除的就是正在查看的笔记,关闭详情if(this.detailNote&&this.detailNote.id===id){this.detailNote=null;this.showDetail=false;}}

边界处理:删除时检查当前详情视图是否展示的就是被删除的笔记,如果是则关闭详情返回列表。

8.3 重置表单

resetForm():void{this.newTitle='';this.newContent='';this.newCategory='生活';}

九、与 ToDo 页面的对比分析

备忘录和待办看起来很相似,但实际差异不小:

维度待办页面备忘录页面
数据字段title + category + prioritytitle + content + category
核心操作切换完成状态查看详情 + 编辑内容
搜索功能❌ 无✅ 双字段搜索
详情视图❌ 无(列表即全部)✅ 独立详情页
编辑功能❌ 无(只能删除)✅ 支持编辑
内容展示单行标题标题 + 内容预览
视图复杂度单一列表列表/详情双视图

备忘录比待办多了一层详情视图,这是 CRUD 中的 “Read/Update” 更完整的体现。


十、技术难点与解决方案

10.1 难点一:多视图状态协调

问题:新增、编辑、详情、列表四种状态如何切换而不冲突?

方案:使用showAddDialogshowDetaileditingNote三个变量组合控制:

  • showAddDialog = true, editingNote = null→ 新增弹窗
  • showAddDialog = true, editingNote != null→ 编辑弹窗
  • showDetail = true→ 详情视图
  • 全部 false/null → 列表视图

10.2 难点二:刷新不丢失编辑状态

问题:ArkTS 的@State只追踪顶层引用变化,修改对象属性不会触发渲染。

方案:使用this.notes = this.notes.slice()[...this.notes]创建新数组引用。

10.3 难点三:搜索性能

问题:每次输入都实时过滤,大数据量下会不会卡?

方案:当前数据量(6条示例数据)毫无压力。如果未来数据量上万,可以加上防抖(debounce):

// 防抖示例(仅当需要时添加)privatesearchTimer:number=-1;onSearch(value:string):void{clearTimeout(this.searchTimer);this.searchTimer=setTimeout(()=>{this.searchText=value;},300);// 300ms 防抖}

十一、本篇总结

核心知识点

知识点实战应用
多视图切换列表/详情/编辑三种视图状态管理
双过滤系统分类筛选 + 关键词搜索
CRUD 完整流程新增、读取、编辑、删除
弹窗复用新增和编辑共享同一弹窗
内容截断列表预览截断 + 详情完整展示
条件渲染if/else控制不同视图

完整用户操作路径

  1. 浏览:进入页面 → 查看所有笔记列表
  2. 搜索:输入关键词 → 实时过滤笔记
  3. 筛选:点击分类标签 → 只看某分类
  4. 查看详情:点击卡片 → 进入详情视图 → 查看完整内容
  5. 编辑:点击 ✏️ → 弹窗编辑 → 保存修改 → 返回详情
  6. 删除:点击 🗑️ → 从列表移除 → 自动返回列表
  7. 新增:点击 “+” → 弹窗输入 → 创建 → 列表新增卡片

十二、下篇预告

最后一篇将开发**「心情日记」页面**,这是最具视觉趣味性的页面,涵盖:

  • 日历网格的纯算法实现
  • 情绪记录的 Emoji 可视化
  • 月度统计与情绪分析
  • 个人中心页面开发

敬请期待最终篇!

http://www.gsyq.cn/news/1524716.html

相关文章:

  • 把Codex玩起来(使用CC Switch接入DeepSeek)
  • 如何永久珍藏你的数字记忆:WeChatMsg免费开源工具终极指南
  • 哪个平台买机票性价比高?同程旅行6大省钱活动实测 - 资讯焦点
  • 新手选牛肉避坑指南:3类常见陷阱 + 五步选购法 - 资讯焦点
  • 武汉音响改装难题破解:武汉声动汽车音响3大优势保驾护航,坦克音响改装/宝马音响改装/汽车音响改装,音响改装旗舰店推荐 - 音响改装门店分享
  • 戴森球计划工厂蓝图库:3000+专业设计方案解析与技术架构深度剖析
  • 5分钟掌握FanControl:Windows平台专业风扇控制软件完全指南
  • 2026年广州靠谱宣传片制作公司推荐:真实案例与服务质量解读 - 资讯焦点
  • 【收券宝】京东E卡即时回收平台推荐,快速变现安全高效率 - 资讯焦点
  • 3步构建记忆型AI助手:OpenAI-Agents Session系统深度解析
  • 新手必看:用GNS3从零搭建四路由器网络,手把手配置RIP和OSPF(含拓扑文件)
  • 2026南京奢侈品包包回收攻略丨实测避坑正规机构盘点 - 薛定谔的梨花猫
  • MPC8245配置寄存器详解:从错误处理到内存控制,构建稳定嵌入式系统
  • MPC7450软件页表搜索:TLB未命中时软件接管内存地址转换的机制详解
  • 2026青岛香港中路名表回收实测,保卡齐全多卖20% - 逸程
  • 5个技巧让Mac Mouse Fix彻底改变你的macOS鼠标体验:从新手到专家
  • 别盲目自建 Milvus:我把向量引擎、RAG 和 API 中转站连续跑了 4 个月,成本与报错率到底差在哪?
  • 如何快速上手IINA:macOS终极视频播放器完整指南
  • 终极指南:如何用KKManager简化Illusion游戏模组管理
  • 技术人转型 AI:从后端工程到 AI 应用的能力迁移路径
  • SillyTavern性能优化指南:3大技巧实现AI聊天响应速度提升60%
  • 2026南京名表回收实测测评:本地7大主流平台实景体验,靠谱渠道深度解析 - 薛定谔的梨花猫
  • Function Calling 工程实践:从工具定义到错误恢复的完整链路
  • 2026深圳钻石回收怎么卖TOP首位,正规变现流程全解析 - 讯息早知道
  • 2026 长沙表包金钻回收店推荐 - 奢侈品回收
  • TEB vs DWA:你的扫地机器人或AGV该选哪个局部避障算法?实战对比与参数调优心得
  • 2026青岛海马VS蓝宝石力士回收保值率对比,本地实测 - 逸程
  • 终极Adobe Illustrator脚本套件:设计师效率提升300%的免费解决方案
  • 告别单调界面:用foobox-cn打造你的专业级音乐播放器
  • 天津高端钻石回收实测,2026年6月资质门店推荐 - 讯息早知道