HarmonyOs应用《重要日》开发第9篇 - 重要日列表页面实现详解 本篇深入剖析 ImportantDayListPage 的完整实现包括页面结构、空状态处理、排序列表、浮动按钮以及响应式数据监听。一、页面功能概述重要日列表页面是应用的第一个 Tab是用户看到的主页面。它的功能包括顶部标题栏统计面板总计/即将到来/已过/本月排序选项栏重要日卡片列表空状态提示右下角浮动添加按钮二、页面结构设计2.1 整体布局┌─────────────────────────────────┐ │ 状态栏安全区域 padding │ ├─────────────────────────────────┤ │ 重要日 │ ← 标题栏 ├─────────────────────────────────┤ │ 总计 3 │ 即将 2 │ 已过 1 │ 本月 1│ ← 统计面板 ├─────────────────────────────────┤ │ 排序: 按日期 ▾ │ ← 排序栏 ├─────────────────────────────────┤ │ ┌───────────────────────────┐ │ │ │ 妈妈生日 30天后 │ │ ← DayCard │ │ 2027年3月15日 每年 │ │ │ └───────────────────────────┘ │ │ ┌───────────────────────────┐ │ │ │ 旅行出发 5天后 │ │ ← DayCard │ │ 2026年7月20日 │ │ │ └───────────────────────────┘ │ │ ┌────┐ │ │ │ │ │ ← FAB │ └────┘ │ └─────────────────────────────────┘2.2 代码实现// pages/ImportantDayListPage.etsComponentV2exportstruct ImportantDayListPage{privatevm:MainViewModelMainViewModel.instance;LocalavoidArea:AvoidAreaAppStorageV2.connect(AvoidArea,()newAvoidArea(0,0))!;LocalsortedDays:ImportantDay[][];aboutToAppear():void{this.refreshList();}Monitor(vm.dayList)onDayListChange():void{this.refreshList();}Monitor(vm.sortType)onSortChange():void{this.refreshList();}privaterefreshList():void{this.sortedDaysthis.vm.getSortedDays();}build(){Stack({alignContent:Alignment.BottomEnd}){Column(){// 标题栏Row(){Text(重要日).fontSize($r(app.float.title_font_size)).fontColor($r(app.color.font_primary)).fontWeight(FontWeight.Bold);}.width(100%).height(56).padding({left:20,right:20}).padding({top:px2vp(this.avoidArea.topRectHeight)});// 统计面板if(this.vm.dayList.length0){Column(){StatPanel();}.width(100%).padding({left:16,right:16,bottom:8});}// 排序栏if(this.vm.dayList.length0){Row(){Text($r(app.string.sort_hint)).fontSize($r(app.string.font_size_12)).fontColor($r(app.color.font_secondary));Text(this.sortTypeLabel()).fontSize($r(app.string.font_size_12)).fontColor($r(app.color.font_primary)).margin({left:4});}.width(100%).padding({left:20,right:20,top:8,bottom:8}).justifyContent(FlexAlign.Start).bindMenu([{value:按日期,action:(){this.vm.setSortType(SortType.BY_DATE);}},{value:按创建时间,action:(){this.vm.setSortType(SortType.BY_CREATED);}},{value:按倒计时,action:(){this.vm.setSortType(SortType.BY_COUNTDOWN);}},]);}// 列表 / 空状态if(this.vm.dayList.length0){this.emptyState();}else{List({space:12}){ForEach(this.sortedDays,(day:ImportantDay){ListItem(){DayCard({day:day})}.onClick((){this.vm.selectedDayIdday.id;router.pushUrl({url:pages/DayDetailPage});});},(day:ImportantDay)day.id);}.width(100%).layoutWeight(1).padding({left:16,right:16,bottom:16}).alignListItem(ListItemAlign.Center);}}.width(100%).height(100%).backgroundColor($r(app.color.page_background));// 浮动添加按钮Button(){Text().fontSize(32).fontColor(Color.White);}.width(56).height(56).borderRadius(28).backgroundColor($r(app.color.icon_checked)).margin({right:20,bottom:20}).onClick((){router.pushUrl({url:pages/AddEditDayPage});});}.width(100%).height(100%);}}三、关键实现细节3.1 响应式数据监听Monitor(vm.dayList)onDayListChange():void{this.refreshList();}Monitor(vm.sortType)onSortChange():void{this.refreshList();}两个独立的Monitorvm.dayList变化时刷新列表添加、删除、更新重要日vm.sortType变化时重新排序用户切换排序方式这种分离确保了不同类型的变化都能触发正确的更新。3.2 排序后的列表副本LocalsortedDays:ImportantDay[][];privaterefreshList():void{this.sortedDaysthis.vm.getSortedDays();}页面维护一个sortedDays副本而非直接使用vm.dayList。原因排序结果需要分离展示避免每次渲染都重新排序ForEach 的 key 生成器需要稳定的列表3.3 条件渲染// 统计面板和排序栏只在有数据时显示if(this.vm.dayList.length0){Column(){StatPanel();}// ...}// 有数据显示列表无数据显示空状态if(this.vm.dayList.length0){this.emptyState();}else{List({space:12}){...}}3.4 排序菜单.bindMenu([{value:按日期,action:(){this.vm.setSortType(SortType.BY_DATE);}},{value:按创建时间,action:(){this.vm.setSortType(SortType.BY_CREATED);}},{value:按倒计时,action:(){this.vm.setSortType(SortType.BY_COUNTDOWN);}},])使用bindMenu绑定弹出菜单。点击排序栏时弹出三个选项选择后调用vm.setSortType()触发Monitor(vm.sortType)自动刷新列表。3.5 排序标签privatesortTypeLabel():string{if(this.vm.sortTypeSortType.BY_DATE)return按日期;elseif(this.vm.sortTypeSortType.BY_CREATED)return按创建时间;elsereturn按倒计时;}3.6 浮动按钮FABStack({alignContent:Alignment.BottomEnd}){Column(){// 主内容}// FABButton(){Text().fontSize(32).fontColor(Color.White);}.width(56).height(56).borderRadius(28).backgroundColor($r(app.color.icon_checked)).margin({right:20,bottom:20}).onClick((){router.pushUrl({url:pages/AddEditDayPage});})}使用Stack的alignContent: Alignment.BottomEnd将 FAB 定位到右下角。FAB 是一个圆形按钮56x56圆角28使用主题蓝色背景。四、空状态设计BuilderemptyState(){Column(){Text().fontSize(64);Text($r(app.string.no_days_title)).fontSize($r(app.string.font_size_20)).fontColor($r(app.color.font_primary)).fontWeight(FontWeight.Medium).margin({top:16});Text($r(app.string.no_days_desc)).fontSize($r(app.string.font_size_14)).fontColor($r(app.color.font_secondary)).margin({top:8});}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center);}空状态包括一个大号日历 emoji64fp还没有重要日标题点击右下角按钮添加你的第一个重要日描述使用justifyContent(FlexAlign.Center)和layoutWeight(1)让空状态内容垂直居中。五、列表项点击处理ListItem(){DayCard({day:day})}.onClick((){this.vm.selectedDayIdday.id;router.pushUrl({url:pages/DayDetailPage});});点击列表项时将选中 ID 存入 ViewModelvm.selectedDayId day.id跳转到详情页面router.pushUrl详情页面通过vm.selectedDayId获取对应的重要日数据。这种通过 ViewModel 传递选中 ID 的方式避免了 URL 参数传递的限制。六、ForEach 的 key 生成器ForEach(this.sortedDays,(day:ImportantDay){ListItem(){DayCard({day:day})}},(day:ImportantDay)day.id);// ↑ key 生成器使用day.id作为 key确保列表更新时正确复用组件删除/插入操作不会导致全量重绘拖拽排序时能正确追踪元素七、样式设计7.1 颜色方案// color.json{name:page_background,value:#F1F3F5}// 浅灰背景{name:card_background,value:#FFFFFF}// 白色卡片{name:font_primary,value:#E6000000}// 主文字90% 黑{name:font_secondary,value:#99000000}// 次要文字60% 黑{name:icon_checked,value:#007DFF}// 主题蓝7.2 尺寸规范// float.json{name:title_font_size,value:24fp}{name:stat_number_font_size,value:28fp}{name:stat_label_font_size,value:12fp}所有尺寸使用fpfont-size pixel和vpvirtual pixel单位确保不同屏幕密度下的一致性。八、页面生命周期页面创建 │ ├── aboutToAppear() │ └── refreshList() → 获取排序后的列表 │ ├── build() 渲染 │ ├── 标题栏 │ ├── 统计面板条件渲染 │ ├── 排序栏条件渲染 │ ├── 列表 / 空状态 │ └── FAB │ ├── Monitor(vm.dayList) → 数据变化时刷新 ├── Monitor(vm.sortType) → 排序变化时刷新 │ └── aboutToDisappear()页面销毁九、小结ImportantDayListPage 是应用的核心页面通过Monitor实现了完全数据驱动的 UI 更新。Stack 布局实现浮动按钮、条件渲染处理空状态、bindMenu 提供排序选项——这些 ArkUI 组件和 API 的组合使用构建出了一个完整的列表页面体验。

相关新闻

最新新闻

C++实现HDLC协议栈:从原理到嵌入式工业通信实战

C++实现HDLC协议栈:从原理到嵌入式工业通信实战

1. 项目概述:为什么要在C里折腾HDLC?如果你在嵌入式、工业通信或者一些老牌通信设备公司的代码里翻过,大概率会碰到HDLC(高级数据链路控制)协议。乍一看,这协议名字里带个“高级”,但其实是上个…

2026/7/16 5:45:43
PCB设计工程师核心技能与学习路径:从入门到进阶

PCB设计工程师核心技能与学习路径:从入门到进阶

这次我们来聊聊PCB设计工程师这个岗位到底需要掌握哪些技能,哪些内容可以暂时放一放。对于刚入行的工程师来说,最头疼的就是不知道学习重点在哪里,担心学了一堆用不上的东西,反而错过了核心技能。PCB设计工程师主要负责电路板的设…

2026/7/16 5:45:43
【AI总结】2026年6月 主流国内外大模型总结

【AI总结】2026年6月 主流国内外大模型总结

目录 引言一、 国际主流模型 1. OpenAI GPT 系列2. Anthropic Claude 系列3. Google Gemini 系列4. Meta Llama 系列5. xAI Grok 系列6. 其他值得关注的国际模型 二、 国内主流模型 1. 百度 文心大模型2. 阿里 通义千问3. 腾讯 混元大模型4. 字节跳动 豆包大模型 / 扣子5. 智谱…

2026/7/16 5:45:43
Java循环控制进阶:while与do..while的实战抉择与break/continue的精准调控

Java循环控制进阶:while与do..while的实战抉择与break/continue的精准调控

1. 为什么需要while和do..while循环?在日常编程中,我们经常遇到需要重复执行某段代码的场景。比如读取用户输入直到输入合法、处理文件中的每一行数据、或者游戏中的主循环。这时候,循环结构就派上用场了。Java提供了三种主要的循环结构&…

2026/7/16 5:45:43
【2014-01-27】cocos2dx学习笔记:CCNode回调流程

【2014-01-27】cocos2dx学习笔记:CCNode回调流程

[历史归档] 本文原发布于 cstriker1407.info 个人博客,内容为历史存档,仅供参考。 发布时间: 2014-01-27 | 标题:cocos2dx学习笔记:CCNode回调流程 | 分类: 编程 / C &&…

2026/7/16 5:45:43
Inno Setup多语言配置实战:从界面翻译到运行时语言切换

Inno Setup多语言配置实战:从界面翻译到运行时语言切换

1. Inno Setup多语言配置的核心价值 当你开发的软件需要面向全球用户时,安装包的多语言支持就成为了必备功能。想象一下,一个日本用户打开全是中文的安装界面,或者德国用户面对英文安装指引时的困惑——这些体验细节往往决定了用户对产品的第…

2026/7/16 5:40:33

月新闻