news 2026/7/18 4:23:39

HarmonyOs应用《重要日》开发第10篇 - DayCard 组件设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOs应用《重要日》开发第10篇 - DayCard 组件设计与实现

本篇深入讲解 ImportantDays 项目中最核心的 UI 组件——DayCard。它展示单个重要日的完整信息,包括标题、描述、日期、倒数天数、颜色标识和置顶标记。

一、DayCard 设计目标

DayCard 是重要日列表中每一条记录的卡片式展示,设计目标:

  1. 信息密度适中:一眼看到标题和倒数天数,次要信息不抢眼
  2. 视觉层级清晰:标题 > 倒数 > 日期 > 描述
  3. 颜色标识:左侧色条快速区分不同重要日
  4. 状态反馈:今天的日期特殊处理(🎉)
  5. 响应式更新:数据变化时自动刷新

二、卡片布局设计

┌─────────────────────────────────────────┐ │ ╆──────────────────────────────────────┐│ │ │ ││ │ │ 妈妈生日 30 ││ │ │ 记得买蛋糕 天后 ││ │ │ 2027年3月15日 每年 ││ │ │ ││ │ └──────────────────────────────────────┘│ └─────────────────────────────────────────┘ ↑ 色条(6vp宽,圆角,对应颜色索引)

三、完整代码分析

3.1 组件定义

// components/DayCard.ets@ComponentV2exportstruct DayCard{@Paramday:ImportantDay=newImportantDay('','','');privatevm:MainViewModel=MainViewModel.instance;@LocalcountValue:number=0;@LocalcountSuffix:string='';@LocalisToday:boolean=false;@LocaleffectiveDate:string='';@LocalrepeatLabel:string='';aboutToAppear():void{this.refreshData();}@Monitor('day.date','day.countMode','day.repeatType')onDayChange():void{this.refreshData();}privaterefreshData():void{constcount=this.vm.getCountText(this.day);this.countValue=count.value;this.countSuffix=count.suffix;this.isToday=count.isToday;this.effectiveDate=this.vm.getEffectiveDate(this.day);if(this.day.repeatType===RepeatType.YEARLY){this.repeatLabel=' 每年';}elseif(this.day.repeatType===RepeatType.MONTHLY){this.repeatLabel=' 每月';}else{this.repeatLabel='';}}// ...}

3.2 @Param 接收数据

@Paramday:ImportantDay=newImportantDay('','','');

父组件ImportantDayListPage传递ImportantDay对象:

DayCard({day:day})

@Param确保当父组件的数据变化时,DayCard 自动更新。

3.3 @Monitor 精确监听

@Monitor('day.date','day.countMode','day.repeatType')onDayChange():void{this.refreshData();}

只监听影响倒数计算的三个属性:

  • day.date:日期变化需要重新计算
  • day.countMode:计数模式变化(倒数/正数)
  • day.repeatType:重复类型变化影响有效日期

其他属性(如titledescription)变化不需要重新计算倒数,所以不监听,减少不必要的刷新。

四、UI 构建详解

4.1 整体结构

build(){Column(){Row(){// 左侧色条Column().width(6).height('100%').backgroundColor(AppConstants.COLOR_PALETTE[this.day.colorIndex%AppConstants.COLOR_PALETTE.length]).borderRadius(3).margin({right:12});// 中间内容区Column(){// 标题Text(this.day.title).fontSize($r('app.string.font_size_16')).fontColor($r('app.color.font_primary')).fontWeight(FontWeight.Medium).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis}).width('100%');// 描述(可选)if(this.day.description){Text(this.day.description).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_secondary')).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis}).width('100%').margin({top:2});}// 日期和标签Row(){Text(DateUtil.formatDateChinese(this.effectiveDate)).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_tertiary'));if(this.repeatLabel){Text(this.repeatLabel).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_tertiary')).margin({left:6});}if(this.day.isPinned){Text('📌').fontSize($r('app.string.font_size_12')).margin({left:6});}}.width('100%').margin({top:4});}.layoutWeight(1).alignItems(HorizontalAlign.Start);// 右侧倒数显示Column(){if(this.isToday){Text('🎉').fontSize(32);Text('今天').fontSize($r('app.string.font_size_12')).fontColor(AppConstants.COLOR_PALETTE[this.day.colorIndex%AppConstants.COLOR_PALETTE.length]).fontWeight(FontWeight.Bold);}else{Text(`${this.countValue}`).fontSize($r('app.string.font_size_32')).fontColor(AppConstants.COLOR_PALETTE[this.day.colorIndex%AppConstants.COLOR_PALETTE.length]).fontWeight(FontWeight.Bold);Text(this.countSuffix).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_secondary'));}}.alignItems(HorizontalAlign.Center).width(80);}.width('100%').padding(16).backgroundColor($r('app.color.card_background')).borderRadius(16);}.width('100%');}

4.2 左侧色条

Column().width(6).height('100%').backgroundColor(AppConstants.COLOR_PALETTE[this.day.colorIndex%AppConstants.COLOR_PALETTE.length]).borderRadius(3).margin({right:12});

色条设计:

  • 宽度 6vp,圆角 3vp(半圆效果)
  • 颜色从 16 色调色板中按colorIndex选取
  • 使用% COLOR_PALETTE.length防止索引越界
  • height('100%')确保与卡片等高

4.3 文本溢出处理

Text(this.day.title).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})

标题限制一行,超出部分显示省略号(...)。这确保长标题不会破坏卡片布局。

4.4 倒数显示区域

// 今天的情况if(this.isToday){Text('🎉').fontSize(32);Text('今天').fontSize($r('app.string.font_size_12')).fontColor(AppConstants.COLOR_PALETTE[...]).fontWeight(FontWeight.Bold);}// 非今天的情况else{Text(`${this.countValue}`)// 大号数字.fontSize($r('app.string.font_size_32')).fontColor(AppConstants.COLOR_PALETTE[...]).fontWeight(FontWeight.Bold);Text(this.countSuffix)// 后缀(天后/天前/天).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_secondary'));}

右侧区域宽度固定 80vp,内容居中对齐:

  • 今天:显示 🎉 emoji + "今天"文字
  • 非今天:大号数字(32fp)+ 小号后缀(12fp)

数字颜色使用与色条相同的颜色,形成视觉关联。

4.5 条件渲染标签

Row(){// 日期(始终显示)Text(DateUtil.formatDateChinese(this.effectiveDate)).fontSize($r('app.string.font_size_12')).fontColor($r('app.color.font_tertiary'));// 重复标签(有重复时显示)if(this.repeatLabel){Text(this.repeatLabel).margin({left:6});}// 置顶标记(置顶时显示)if(this.day.isPinned){Text('📌').margin({left:6});}}

底部信息行按需显示标签,保持简洁。

五、16 色调色板

// common/Constants.etspublicstaticreadonlyCOLOR_PALETTE:string[]=['#FF6B6B',// 珊瑚红'#FF8E53',// 橙红'#FFA94D',// 橙色'#FFD43B',// 金黄'#A9E34B',// 黄绿'#51CF66',// 翠绿'#20C997',// 青绿'#22B8CF',// 青色'#339AF0',// 天蓝'#5C7CFA',// 蓝紫'#845EF7',// 紫色'#CC5DE8',// 品红'#F06595',// 粉红'#FF6B6B',// 珊瑚红(重复)'#868E96',// 灰色'#495057',// 深灰];

调色板设计特点:

  • 16 种颜色覆盖红、橙、黄、绿、青、蓝、紫、粉、灰
  • 按色环排列,相邻颜色有渐变关系
  • 适合用Grid8列布局展示

六、数据刷新机制

数据变化流程: 1. 用户在编辑页修改重要日 └── vm.updateDay(day) └── dayList[index] = day └── @Trace dayList 触发 └── ImportantDayListPage @Monitor('vm.dayList') └── refreshList() └── sortedDays = vm.getSortedDays() └── ForEach 重新渲染 └── DayCard({ day: newDay }) └── @Param 更新 day └── @Monitor('day.date', ...) └── refreshData() └── 更新 countValue 等 @Local └── UI 自动刷新

七、样式细节

7.1 卡片外观

.padding(16)// 16vp 内边距.backgroundColor($r('app.color.card_background'))// 白色背景.borderRadius(16)// 16vp 圆角

7.2 字体层级

元素字号颜色粗细
标题16fpfont_primary (#E6000000)Medium
描述12fpfont_secondary (#99000000)Normal
日期12fpfont_tertiary (#66000000)Normal
倒数数字32fp色条颜色Bold
倒数后缀12fpfont_secondary (#99000000)Normal

7.3 间距设计

.margin({right:12})// 色条到内容的间距.margin({top:2})// 标题到描述的间距.margin({top:4})// 描述到日期行的间距.margin({left:6})// 标签之间的间距

八、性能优化

8.1 精确的 @Monitor

只监听影响显示的属性,避免day.description变化时也触发refreshData()

8.2 固定宽度的右侧区域

.width(80)// 固定宽度

固定宽度避免了不同卡片之间因数字位数不同导致的布局抖动。

8.3 maxLines 限制

.maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})

限制标题和描述为一行,确保卡片高度一致,列表滚动更流畅。

九、小结

DayCard 是 ImportantDays 项目中最核心的展示组件。通过@Param接收数据、@Monitor精确监听变化、@Local管理派生状态,实现了完全响应式的卡片展示。左侧色条 + 右侧大号倒数的布局设计,在有限空间内传递了丰富的信息。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 4:23:31

市面上有哪些服务较好的外贸建站企业呢?

在当前的市场中,有不少服务较好的外贸建站企业,上海凰启是其中颇具代表性的一家,此外还有其他一些知名企业。以下为你具体介绍:上海凰启信息技术有限公司上海凰启出海成立于 2016 年,专注于外贸互联网整网效果智能营销…

作者头像 李华
网站建设 2026/7/18 4:22:27

面向对象进阶(抽象类接口内部类)

第一章 抽象类1.1 概述1.1.1 抽象类引入​ 父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了(因为子类对象会调用自己重写的方法)。换句话…

作者头像 李华
网站建设 2026/7/18 4:22:17

PySpark MLlib工业级文本分类实战:亿级舆情六分类系统搭建

1. 项目概述:为什么用 PySpark MLlib 做文本分类,而不是 Scikit-learn 或 Hugging Face?你有没有遇到过这样的场景:公司每天从微博、小红书、抖音评论区抓取 2000 万条用户反馈,要实时判断情绪倾向——是“强烈不满”“…

作者头像 李华
网站建设 2026/7/18 4:22:14

Plotly条形图决策指南:从认知建模到交互式可视化

1. 为什么我坚持用 Plotly 做条形图——一个从业十年的数据可视化老手的真心话你有没有过这种体验:辛辛苦苦跑通模型、清洗完数据,结果一到画图环节就卡住?不是颜色配得像打翻的调色盘,就是坐标轴标签挤成一团看不清,再…

作者头像 李华
网站建设 2026/7/18 4:21:18

Turbowarp文本换行问题解析与解决方案

在Turbowarp中处理文本换行时,很多开发者会遇到一个常见问题:明明在代码中使用了换行符,但显示效果却不尽如人意。本文将深入解析Turbowarp中换行符的处理机制,并提供多种实用的解决方案。1. 理解换行符的基本概念1.1 回车与换行的…

作者头像 李华
网站建设 2026/7/18 4:20:38

Rust在ARM Cortex-M嵌入式开发中的实践指南

1. Rust与ARM Cortex-M开发的背景与优势Rust语言在嵌入式领域正以惊人的速度崛起。根据2023年嵌入式开发者调查报告,使用Rust进行嵌入式开发的比例相比前一年增长了47%。这种增长在Cortex-M系列开发中尤为明显,原因在于Rust完美解决了传统嵌入式开发的几…

作者头像 李华