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

鸿蒙原生应用实战(二):首页开发与全局数据流设计

鸿蒙原生应用实战(二):首页开发与全局数据流设计

系列目录:

  • 第一篇:项目搭建与页面架构设计
  • 第二篇:首页开发与全局数据流设计 ← 当前
  • 第三篇:笔记详情与编辑页面的路由与CRUD
  • 第四篇:分类浏览与个人中心的多维数据展示
  • 第五篇:构建调试、异常处理与HAP发布

一、前言

上一篇我们搭建了项目骨架并设计了 5 页面的路由架构。本篇将正式进入编码——开发 App 的首页(Index.ets),它是整个应用的门面,承载着笔记列表展示、搜索、分类筛选和新建入口等核心功能。

同时,我们将设计全局数据流方案,让 5 个页面共享同一份笔记数据。

二、首页布局架构

首页是一个全屏Stack布局,包含三层:

Stack (根容器) ├── Column (主内容层) │ ├── 顶部导航栏 (Row) │ ├── 搜索栏 (Row) │ ├── 分类筛选标签 (Scroll → Row) │ ├── 笔记列表 (List) 或 空状态 (Column) │ └── 底部导航栏 (Row) └── Column (悬浮FAB按钮层) └── 圆形新建按钮

使用Stack作为根容器的好处:FAB 按钮可以通过.align(Alignment.BottomEnd)轻松定位到右下角,不会影响主内容区的布局。

三、数据模型与全局状态

3.1 定义 Note 接口

在 Index.ets 顶部定义数据模型:

interfaceNote{id:number;title:string;content:string;category:string;// '工作' | '学习' | '生活' | '灵感'date:string;// 'YYYY-MM-DD'}

3.2 AppStorage 全局状态

鸿蒙的AppStorage是应用级的 UI 状态存储,跨页面共享。我们用它来存储笔记数组:

// 写入(任意页面)AppStorage.setOrCreate<string>('notes',JSON.stringify(allNotes));// 读取(任意页面)letstored:string|undefined=AppStorage.get<string>('notes');letnotes:Note[]=stored?JSON.parse(stored)asNote[]:[];

为什么用 JSON 字符串而不是直接存对象数组?
因为AppStorage对复杂对象的序列化支持有限,存储 JSON 字符串是跨版本最稳定的方式。

3.3 预置示例数据

为了让 App 第一次启动时有内容可看,在getDefaultNotes()中预置 6 条笔记:

getDefaultNotes():Note[]{letdefaultNotes:Note[]=[{id:1,title:'鸿蒙开发入门指南',content:'HarmonyOS 是面向全场景的分布式操作系统...',category:'学习',date:'2024-12-01'},{id:2,title:'项目周报 - 第十二周',content:'本周完成:1. 用户模块接口联调 2. 首页UI重构...',category:'工作',date:'2024-12-15'},// ... 更多示例数据];returndefaultNotes;}

整个数据流的生命周期:

App 启动 → aboutToAppear() → loadNotes() ├── AppStorage 有数据?→ 读取 → filterNotes() └── 无数据?→ 预置数据 → 存入 AppStorage → filterNotes()

四、顶部导航栏实现

// 顶部导航栏Row(){Text($r('app.string.page_title_home'))// "我的笔记".fontSize($r('app.float.title_font_size')).fontWeight(FontWeight.Bold).fontColor($r('app.color.text_primary'))Blank()Image($r('app.media.foreground'))// 头像图标.width(28).height(28).borderRadius(14).onClick(()=>this.goToProfilePage())}.width('100%').padding({left:$r('app.float.page_padding'),right:$r('app.float.page_padding')})

这里使用了$r()引用资源文件,ArkTS 编译时会自动替换为实际值。注意Image这里用了默认资源的占位图,实际开发中应替换为自定义头像。

五、搜索栏实现

搜索栏有点击展开/收起两种状态:

@StateisSearchActive:boolean=false;@StatesearchText:string='';// 搜索栏Row(){if(this.isSearchActive){TextInput({placeholder:'搜索笔记...',text:this.searchText}).layoutWeight(1).height(40).onChange((value:string)=>this.onSearchChange(value))}else{Text('搜索笔记...').fontColor($r('app.color.text_tertiary')).layoutWeight(1)}// 搜索图标Image($r('app.media.foreground')).onClick(()=>this.toggleSearch())}

搜索触发过滤逻辑:

filterNotes():void{letresult:Note[]=this.notes;// 分类过滤if(this.selectedCategory!=='全部'){result=result.filter((note:Note)=>note.category===this.selectedCategory);}// 关键词过滤(标题+正文)if(this.searchText.length>0){letkeyword:string=this.searchText.toLowerCase();result=result.filter((note:Note)=>note.title.toLowerCase().includes(keyword)||note.content.toLowerCase().includes(keyword));}this.filteredNotes=result;}

六、分类筛选标签

5 个分类标签用横向滚动的Scroll实现:

privatecategories:CategoryItem[]=[{label:'全部',key:'全部'},{label:'工作',key:'工作'},{label:'学习',key:'学习'},{label:'生活',key:'生活'},{label:'灵感',key:'灵感'}];Scroll(){Row(){ForEach(this.categories,(item:CategoryItem)=>{Text(item.label).padding({left:16,right:16,top:6,bottom:6}).backgroundColor(this.selectedCategory===item.label?$r('app.color.primary')// 选中时蓝色:$r('app.color.card_bg')// 未选中时白色).fontColor(this.selectedCategory===item.label?Color.White:$r('app.color.text_secondary')).borderRadius(16).onClick(()=>this.onCategoryChange(item.label))},(item:CategoryItem)=>item.key)}}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)

ForEach的第三个参数(item) => item.key是键值生成器,帮助 ArkTS 高效地 diff 和重用组件。

七、笔记卡片列表

每个笔记卡片包含:分类标签、标题、内容预览、日期。

List(){ForEach(this.filteredNotes,(note:Note)=>{ListItem(){Column(){// 分类标签Text(note.category).fontColor(this.getCategoryColor(note.category)).border({width:1,color:this.getCategoryColor(note.category)}).borderRadius(4).alignSelf(ItemAlign.Start)// 标题 - 最多1行Text(note.title).fontSize($r('app.float.subtitle_font_size')).fontWeight(FontWeight.Medium).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})// 预览 - 最多2行Text(note.content).fontSize($r('app.float.small_font_size')).fontColor($r('app.color.text_secondary')).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})// 日期Text(note.date).fontSize($r('app.float.tiny_font_size')).fontColor($r('app.color.text_tertiary'))}.padding(14).backgroundColor($r('app.color.card_bg')).borderRadius($r('app.float.card_radius'))}.onClick(()=>this.goToNoteDetail(note))},(note:Note)=>note.id.toString())}

空状态处理

当过滤结果为空时,显示友好的空状态:

if(this.filteredNotes.length>0){// ... 列表}else{Column(){Text('还没有笔记,点击下方按钮新建').fontColor($r('app.color.text_tertiary'))}.layoutWeight(1).justifyContent(FlexAlign.Center)}

八、悬浮新建按钮(FAB)

利用Stack的特性,将 FAB 放在主内容层之上:

Stack(){// 主内容 Column ...Column().width('100%').height('100%)// FABColumn(){/* 加号图标 */}.width(56).height(56).backgroundColor($r('app.color.primary')).borderRadius(28).onClick(()=>this.goToEditPage()).align(Alignment.BottomEnd).margin({bottom:72,right:24})}

注意:早期版本我用过.overlay()的 inline builder 方案,但 ArkTS 严格模式不允许在.overlay()参数中使用内联 builder。改用Stack+.align()是最简洁可靠的方案。

九、底部导航栏

三个底部 Tab 使用简单的文字 + 点击跳转,当前页面高亮:

Row(){Text('笔记').fontColor($r('app.color.primary'))// 高亮当前页Text('分类').onClick(()=>this.goToCategoryPage())Text('我的').onClick(()=>this.goToProfilePage())}.justifyContent(FlexAlign.SpaceAround).backgroundColor($r('app.color.card_bg'))

十、onPageShow 生命周期

鸿蒙的页面生命周期中,onPageShow在每次页面可见时调用。我们在首页和分类页都注册了它:

aboutToAppear():void{this.loadNotes();// 首次加载}onPageShow():void{this.loadNotes();// 从其他页面返回时刷新}

这样当用户从编辑页面保存笔记返回后,首页会自动刷新列表。

十一、ArkTS 严格模式避坑

11.1 对象字面量必须有类型

// ❌ 错误:arkts-no-untyped-obj-literalsprivatecategories=[{label:'全部',key:'全部'}];// ✅ 正确:定义接口并用类型注解interfaceCategoryItem{label:string;key:string;}privatecategories:CategoryItem[]=[{label:'全部',key:'全部'}];

11.2 数组字面量必须可推断类型

// ✅ 正确:数组类型明确@Statenotes:Note[]=[];@StatefilteredNotes:Note[]=[];

11.3 build() 方法只能有一个根节点

// ❌ 错误:build 中不能有 let 语句build(){letx=1;// 编译错误!Column(){}}// ✅ 正确:在变量声明处计算,或直接内联build(){Column(){}}

十二、本篇总结

本篇我们完成了:

  1. ✅ 首页的完整布局:导航栏、搜索栏、分类标签、笔记列表、FAB
  2. ✅ 基于 AppStorage 的全局数据流设计,5 页面共享数据
  3. ✅ 搜索+分类双重过滤逻辑
  4. ✅ 空状态处理、生命周期刷新
  5. ✅ ArkTS 严格模式避坑指南

下一篇将进入笔记详情页和编辑页面的开发,重点讲解路由参数传递、CRUD 操作和删除确认弹窗。


作者:AtomCode
GitHub: [项目链接]
如果本文对你有帮助,请点赞👍收藏⭐关注➕

http://www.jsqmd.com/news/987736/

相关文章:

  • 【Uniapp 插件 una-banner 】轮播图 / banner / swiper
  • 在Python中,不可变(immutable)数据类型是指一旦创建后,其内容不能被修改的数据类型
  • 2026 香奈儿名牌包包回收规则,无锡门店回收对哪些款式更热忠 - 奢侈品回收评测
  • 分公司越来越多,网络越用越卡?教你用“智能网关”把企业专线成本砍掉50%
  • 宁波精装房石材改造指南:不砸不拆怎么提升质感(2026版) - 宁波融诚石业
  • YOLOv5 实战:不修改 `detect.py`,让检测结果图中的置信度随机显示为自己想要的
  • 零基础玩转大数据!800万条浏览器行为数据ETL实战,从零搞定可视化大屏底层数据
  • 2026年 膜分离设备厂家推荐:辽宁膜分离工艺与管式膜分离系统,青花椒油分离/食品级膜分离/医药膜分离技术优选指南 - 品牌发掘
  • 2026上海百达翡丽回收价格怎么算?全套、单表、成色差价明细解析 - 奢侈品回收评测
  • 多 Agent 协作的“终极难题”:如何解决冲突、分歧与无限循环?
  • 素材去水印实用技巧:按场景选择工具,兼顾效率与画质
  • Web分布式网站架构之-Squid缓存【20260608】005篇-【传统代理】
  • 知识图谱 Graph Rag 方法横向对比
  • 伺服电机仿真(3):PMSM在三相静止坐标系下的数学模型
  • 系统规划与管理师选老师:案例不好的人,需要什么样的老师?
  • 清理重复文件释放C盘空间的工具
  • 新装修的石材为什么开裂?5大原因+预防措施(2026版) - 宁波融诚石业
  • 2026窑炉气体分析系统选购指南:全国优质厂家排名与实战经验分享 - 品研笔录
  • 【UE5】雷达覆盖区域效果
  • 2026年 黑龙江铝塑铝门窗/哈尔滨保暖铝塑铝门窗推荐榜:高密封、抗老化、高性价比家装与老旧小区改造优选 - 品牌发掘
  • 闲置多年奢侈品腕表,2026无锡手表回收如何养护价值更高 - 奢侈品回收评测
  • Python之encrypti0n包语法、参数和实际应用案例
  • Web分布式网站架构之-Squid缓存【20260609】squid配置文件详解001篇
  • SQL/NoSQL数据库为何成为TVA的记忆系统(7)
  • 如何利用 OpenCV 将图像显示在对话框窗口上
  • 2026年苏州定制家具厂家推荐榜:酒店餐饮、适老化、医养机构与养老院圆角防撞星级配套家具精选 - 品牌发掘
  • 网络请求基础:使用http模块发起GET/POST请求(12)
  • 深圳卡地亚回收避坑要点|先查资质、再看报价、最后结算 - 奢侈品回收测评
  • 全固态电池技术路线解析,硫化物、氧化物、聚合物谁主沉浮?
  • 伺服电机仿真(4):PMSM在d-q旋转坐标系下的状态方程与等效电路