鸿蒙原生应用实战(四):收藏页面与底部导航实现——状态管理与跨页面交互
2026/6/8 14:37:47 网站建设 项目流程

鸿蒙原生应用实战(四):收藏页面与底部导航实现——状态管理与跨页面交互

前言

前三章我们完成了首页、诗词库、详情页和作者天地四个页面的开发。本章将完成最后一个核心页面——收藏页面,并实现全局底部导航栏的整合。

收藏页面的实现涉及了数据筛选、编辑模式、分类分组等复杂交互,是检验 ArkTS 状态管理能力的试金石。

一、收藏页面(CollectionPage.ets)

1.1 页面布局总览

┌──────────────────────────┐ │ 我的收藏 编辑 返回 │ ← 顶部栏 ├──────────────────────────┤ │ ⭐ 6首 │ ← 收藏统计 │ 共收藏诗词 │ ├──────────────────────────┤ │ 收藏分类 │ │ 📚全部 📜唐诗 🌸宋词 │ ← 横向分类标签 │ 🎭元曲 📗诗经 🌙五代词 │ ├──────────────────────────┤ │ 📖 静夜思 五言绝句 │ │ 唐 · 李白 │ ← 收藏卡片 │ "思乡名篇,百读不厌" │ │ 收藏于 2025-06-15 │ ├──────────────────────────┤ │ 📖 水调歌头 词 │ │ 宋 · 苏轼 │ │ "中秋绝唱,意境超然" │ │ 收藏于 2025-06-14 │ ├──────────────────────────┤ │ ...(共 6 条) │ ├──────────────────────────┤ │ 🏠首页 📚诗词库 👤作者 ⭐收藏 │ └──────────────────────────┘

1.2 数据结构

收藏数据包含诗词基本信息、收藏日期和用户笔记:

interfaceCollectionItem{id:number;title:string;author:string;dynasty:string;type:string;dateAdded:string;// 收藏日期notes:string;// 用户笔记}// 收藏分类分组interfaceCollectionGroup{name:string;icon:string;count:number;}

1.3 收藏分类分组

我们将收藏的诗词按朝代分类,每个分类显示对应的 emoji 和计数:

constgroups:CollectionGroup[]=[{name:'唐诗',icon:'📜',count:1},{name:'宋词',icon:'🌸',count:3},{name:'元曲',icon:'🎭',count:1},{name:'诗经',icon:'📗',count:1},{name:'五代词',icon:'🌙',count:1}];

1.4 数据过滤实现

收藏页面的数据过滤同样使用@State + @Watch模式:

@State@Watch('onGroupChange')selectedGroup:string='';@StateeditMode:boolean=false;@StatefilteredList:CollectionItem[]=myCollections;onGroupChange():void{if(this.selectedGroup===''){this.filteredList=myCollections;return;}constdynastyMap:Record<string,string>={'唐诗':'唐','宋词':'宋','元曲':'元','诗经':'先秦','五代词':'五代'};consttargetDynasty:string=dynastyMap[this.selectedGroup]||'';this.filteredList=myCollections.filter((c:CollectionItem)=>c.dynasty===targetDynasty);}

1.5 分类标签高亮

分类标签使用@State驱动的条件样式:

// "全部"标签Column(){Text('📚').fontSize(28)Text('全部').fontSize(11).fontColor(this.selectedGroup===''?$r('app.color.accent_purple'):$r('app.color.text_primary')).fontWeight(this.selectedGroup===''?FontWeight.Bold:FontWeight.Normal)Text(myCollections.length.toString()).fontSize(10).fontColor($r('app.color.text_secondary'))}.width(60).alignItems(HorizontalAlign.Center).onClick(()=>{this.selectedGroup='';}).backgroundColor(this.selectedGroup===''?$r('app.color.accent_purple')+'10':Color.Transparent).borderRadius(12)// 各分类标签ForEach(groups,(g:CollectionGroup)=>{Column(){Text(g.icon).fontSize(28)Text(g.name).fontSize(11).fontColor(this.selectedGroup===g.name?$r('app.color.accent_purple'):$r('app.color.text_primary'))Text(g.count.toString()+'首').fontSize(10).fontColor($r('app.color.text_secondary'))}.onClick(()=>{this.selectedGroup=g.name;}).backgroundColor(this.selectedGroup===g.name?$r('app.color.accent_purple')+'10':Color.Transparent)},(g:CollectionGroup)=>g.name)

1.6 收藏卡片设计

每条收藏记录展示完整信息,包含用户笔记(斜体显示):

@BuildercreateCollectionCard(item:CollectionItem){Row(){// 编辑模式下的选中框if(this.editMode){Circle().width(22).height(22).stroke($r('app.color.accent_purple')).strokeWidth(2).fill(Color.Transparent).margin({right:10})}Column(){Text('📖').fontSize(28)}Column(){// 标题 + 类型标签Row(){Text(item.title).fontSize(17).fontWeight(FontWeight.Bold)Text(item.type).fontSize(10).fontColor($r('app.color.accent_purple')).padding({left:6,right:6,top:2,bottom:2}).backgroundColor($r('app.color.accent_purple')+'15').borderRadius(4).margin({left:8})}Text(item.dynasty+' · '+item.author).fontSize(12).fontColor($r('app.color.text_secondary'))// 用户笔记(斜体显示)Text(item.notes).fontSize(13).fontColor($r('app.color.text_primary')).fontStyle(FontStyle.Italic).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text('收藏于 '+item.dateAdded).fontSize(11).fontColor($r('app.color.text_secondary'))}.layoutWeight(1).padding({left:12})if(!this.editMode){Text('>').fontSize(18).fontColor($r('app.color.text_secondary'))}}.width('100%').padding(14).backgroundColor($r('app.color.bg_card')).borderRadius(12).onClick(()=>{if(!this.editMode){router.pushUrl({url:'pages/PoemDetailPage',params:{poemId:item.id}});}})}

1.7 空状态处理

当筛选结果为空时,展示友好的空状态提示:

if(this.filteredList.length===0){Column(){Text('📭').fontSize(48)Text('暂无收藏').fontSize(16).fontColor($r('app.color.text_secondary')).margin({top:12})}.width('100%').height(200).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)}

二、编辑模式实现

收藏页面支持编辑模式,点击顶部"编辑"按钮切换:

Row(){Text('我的收藏').fontSize(24).fontWeight(FontWeight.Bold)Blank()Text(this.editMode?'完成':'编辑').fontSize(14).fontColor($r('app.color.accent_purple')).onClick(()=>{this.editMode=!this.editMode;})}

编辑模式下,每个卡片左侧显示圆形选中框,方便用户批量操作。

三、底部导航栏

3.1 统一设计

所有 5 个页面底部共享同一套导航栏,包含 4 个 Tab:首页、诗词库、作者、收藏。

导航栏放在页面的最底层,使用shadow属性创建阴影效果:

Row(){this.navItem('🏠','首页','home',activePage,'pages/Index')this.navItem('📚','诗词库','list',activePage,'pages/PoemListPage')this.navItem('👤','作者','author',activePage,'pages/AuthorPage')this.navItem('⭐','收藏','collection',activePage,'pages/CollectionPage')}.width('100%').height(60).backgroundColor($r('app.color.bg_card')).padding({top:6,bottom:6}).shadow({radius:8,color:'#15000000',offsetX:0,offsetY:-2// 向上投影,浮在页面上方})

3.2 Tab 高亮逻辑

当前页面对应的 Tab 使用主题色,其他 Tab 使用灰色:

Text(label).fontSize(10).fontColor(page===activePage?$r('app.color.accent_purple'):$r('app.color.text_secondary'))

3.3 Tab 点击跳转

点击非当前 Tab 时触发页面跳转,点击当前 Tab 不做任何操作:

.onClick(()=>{if(page!==activePage){router.pushUrl({url:route});}})

四、ArkTS 中的响应式数据绑定

4.1 @State 装饰器

@State是 ArkTS 中最基础的响应式装饰器,被修饰的变量变化时会触发 UI 重新渲染:

@Componentstruct CollectionPage{@StateeditMode:boolean=false;@StatefilteredList:CollectionItem[]=myCollections;// ...}

4.2 @Watch 装饰器

@Watch用于监听@State变量的变化,执行副作用逻辑:

@State@Watch('onGroupChange')selectedGroup:string='';

关键点@Watch必须直接修饰在@State变量上,不能单独使用。当selectedGroup变化时,onGroupChange方法自动被调用。

4.3 状态管理的完整流程

用户交互(点击分类标签) │ ▼ this.selectedGroup = '宋词' │ ├─→ UI 自动重渲染(分类标签高亮变化) │ └─→ @Watch 触发 onGroupChange() │ ▼ 执行过滤逻辑 │ ▼ this.filteredList = [...] │ └─→ UI 自动重渲染(收藏列表更新)

五、运行错误修复:get 访问器问题

在实际运行中,我们遇到了一个严重的运行时错误:

TypeError: Cannot read property length of undefined

错误根因:在 ArkTS 的动态模式下(arkTSMode: dynamic),get访问器的返回值在模板绑定中无法被正确识别为响应式数据。当在build()方法中使用this.filteredCollections.lengthForEach(this.filteredCollections, ...)时,this.filteredCollections返回的是undefined

解决方案:统一使用@State+@Watch替代get访问器:

页面原方案修复方案
CollectionPageget filteredCollections()@State filteredList+@Watch('onGroupChange')
PoemListPageget filteredPoems()@State filteredList+@Watch('onFilterChange')
AuthorPageget filteredAuthors()@State filteredAuthorsList+@Watch('onAuthorFilterChange')
PoemDetailPageget poemData()@State poemData+@Watch('onPoemIdChange')

这个修复经验非常重要——在 API 23 的 ArkTS 动态模式下,凡是需要在模板中使用的计算数据,都应该用 @State 存储,用 @Watch 触发更新,而不是依赖get访问器。

小结

本章完成了收藏页面的开发,实现了:

  • 收藏数据的分组分类展示
  • 分类筛选与高亮交互
  • 编辑模式切换
  • 全局底部导航栏的统一设计
  • @State + @Watch 响应式数据管理的最佳实践
  • get 访问器在动态模式下的问题与修复

至此,应用的所有 5 个页面已经开发完毕。下一章将总结整个开发过程中的编译错误修复和调试经验。


【系列目录】

  • (一)项目初始化与架构设计
  • (二)首页与诗词库页面开发
  • (三)诗词详情与作者天地页面开发
  • (四)收藏页面与底部导航实现 ← 本文
  • (五)编译调试与问题修复经验

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询