鸿蒙应用开发实战【19】— 统计卡片组件:数字展示、颜色语义与点击导航
鸿蒙应用开发实战【19】— 统计卡片组件:数字展示、颜色语义与点击导航
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
号码助手首页顶部有三张统计卡片,分别显示「待换绑」「待注销」「已停用」三个维度的数量。每张卡片有一个带颜色背景的图标、一个醒目的数字,以及点击后跳转到对应状态列表的交互。这个看似简单的组件,涉及颜色语义系统、@Builder参数化组件、布局等分技巧、animateTo 数字动效等多个关键知识点。
本篇涵盖:StatCard @Builder 参数化设计、AppColors 颜色语义体系、WARN/DANGER/MUTED 三色语义、圆角图标容器实现、layoutWeight 等分布局、数字大字样式、animateTo 数字切换动效、onClick 状态筛选导航。
图1:StatCard 三卡等分布局、WARN/DANGER/MUTED 三色语义、animateTo 数字动效流程
一、统计卡片整体设计
1.1 视觉规格
| 属性 | 规格 |
|---|---|
| 卡片背景 | #E6FFFFFF(白色高亮卡 AppColors.CARD_B) |
| 卡片圆角 | radius: 16 |
| 图标容器 | 26×26 vp,radius: 8,半透明色背景 |
| 数字字号 | fontSize: 22,fontWeight: BOLD |
| 标签字号 | fontSize: 11,fontColor: TEXT_2 |
| 三卡等分 | 父 Row 的每张卡用.layoutWeight(1) |
1.2 三维语义颜色
// 三张卡对应的颜色语义this.StatCard('↔',this.pendingSwitch,'待换绑',AppColors.WARN_BG,AppColors.WARN,'待换绑')this.StatCard('×',this.pendingCancel,'待注销',AppColors.DANGER_BG,AppColors.DANGER,'待注销')this.StatCard('–',this.disabledCount,'已停用',AppColors.MUTED_BG,AppColors.MUTED,'已停用')| 状态 | 图标色 | 背景色(bg) | 含义 |
|---|---|---|---|
| 待换绑 | WARN #DB8A00(警告黄) | WARN_BG #21DB8A00 | 需要用户主动换绑卡号 |
| 待注销 | DANGER #E5395B(危险红) | DANGER_BG #1FE5395B | 需要联系服务商注销 |
| 已停用 | MUTED #8B93A7(静默灰) | MUTED_BG #0F141E3C | 已不再使用,无需紧急处理 |
二、AppColors 颜色语义体系
2.1 语义色定义
// AppColors.ets — 辅助语义色exportclassAppColors{/** 警告黄 */staticreadonlyWARN:string='#DB8A00'/** 警告背景 rgba(219,138,0,.13) — ArkUI #AARRGGBB 格式 */staticreadonlyWARN_BG:string='#21DB8A00'/** 危险红 */staticreadonlyDANGER:string='#E5395B'/** 危险背景 rgba(229,57,91,.12) */staticreadonlyDANGER_BG:string='#1FE5395B'/** 静默灰(不再活跃) */staticreadonlyMUTED:string='#8B93A7'/** 静默背景 rgba(20,30,60,.06) */staticreadonlyMUTED_BG:string='#0F141E3C'}2.2 ArkUI 颜色格式提醒
// ArkUI 的颜色格式是 #AARRGGBB(Alpha 在最前),不是 CSS 的 #RRGGBBAA// 示例:rgba(219,138,0, 0.13) → Alpha=0.13*255≈33 → 0x21// 所以 WARN_BG = '#21DB8A00'// ^^ — Alpha=0x21=33/255≈13%// ^^^^^^ — RGB=DB8A00(警告黄)// Alpha 十六进制速查:// 0xFF=100% 0xCC=80% 0x99=60% 0x66=40% 0x33=20% 0x1A=10%// 0x21≈13% 0x1F≈12% 0x14≈8% 0x0F≈6%2.3 为什么要语义分离
// ❌ 反模式:直接写死颜色字符串Column(){Text(icon).fontColor('#DB8A00')// 危险:多处散落,难以统一调整}.backgroundColor('#21DB8A00')// 修改时要找所有地方// ✅ 正确:通过 AppColors 统一管理语义色Column(){Text(icon).fontColor(AppColors.WARN)}.backgroundColor(AppColors.WARN_BG)// 一处修改,全局生效三、StatCard @Builder 完整实现
3.1 完整代码
// HomePage.ets — StatCard @Builder 方法@BuilderprivateStatCard(icon:string,num:number,label:string,bg:string,fg:string,status:string){Column(){// ① 图标容器(小圆角正方形,半透明色背景)Column(){Text(icon).fontSize(14).fontColor(fg).fontWeight(FontWeight.Bold)}.width(26).height(26).borderRadius(8).backgroundColor(bg)// WARN_BG / DANGER_BG / MUTED_BG.justifyContent(FlexAlign.Center).margin({bottom:10})// ② 数字(主要信息,醒目大字)Text(`${num}`).fontSize(22).fontWeight(AppFonts.WEIGHT_BOLD).fontColor(AppColors.TEXT).lineHeight(22).margin({bottom:4})// ③ 标签(辅助说明)Text(label).fontSize(11).fontColor(AppColors.TEXT_2)}.layoutWeight(1)// 三卡等分宽度.padding({top:12,bottom:12,left:10,right:10}).alignItems(HorizontalAlign.Start).backgroundColor(AppColors.CARD_B).border({width:1,color:AppColors.LINE,radius:16}).onClick(()=>{constparams:StatusRouteParams={status:status}this.navigate('pages/StatusListPage',params)})}3.2 调用方式
// 父容器:Row 横排,space: 10 作为间距Row({space:10}){this.StatCard('↔',this.pendingSwitch,'待换绑',AppColors.WARN_BG,AppColors.WARN,'待换绑')this.StatCard('×',this.pendingCancel,'待注销',AppColors.DANGER_BG,AppColors.DANGER,'待注销')this.StatCard('–',this.disabledCount,'已停用',AppColors.MUTED_BG,AppColors.MUTED,'已停用')}.width('100%').padding({left:16,right:16}).margin({top:4,bottom:16})四、layoutWeight 等分布局
4.1 三卡等分原理
// Row 父容器总宽度 = 屏幕宽度 - 左右 padding - 两个 space// 减去 space: 10 * 2 = 20vp,减去 padding: 16*2 = 32vp// 剩余 = (屏幕宽度 - 32 - 20) / 3 —— 每张卡的宽度// .layoutWeight(1) 对等分非常有用:// 三张卡都写 layoutWeight(1) → 1:1:1 等分剩余空间Row({space:10}){CardA().layoutWeight(1)CardB().layoutWeight(1)CardC().layoutWeight(1)}// 如果要 2:1:1 比例:Row({space:10}){CardA().layoutWeight(2)// 占 50%CardB().layoutWeight(1)// 占 25%CardC().layoutWeight(1)// 占 25%}4.2 避免使用固定宽度
// ❌ 反模式:写死宽度(多屏尺寸适配差)Column().width(100)// 在不同屏幕宽度下看起来不一致// ✅ 推荐:layoutWeight 自适应Column().layoutWeight(1)// 随父容器自适应五、数字动效:animateTo
5.1 为什么要动效
数字从 0 变为实际数字(如 12)时,直接刷新会有突兀感。通过animateTo可以实现数字平滑切换的视觉效果。
5.2 带动效的数字更新
// 在 loadData 成功后,使用 animateTo 驱动数字变化@StatependingSwitch:number=0@StatependingCancel:number=0@StatedisabledCount:number=0privateasyncloadData():Promise<void>{try{constcounts=awaitAppBindingDao.countByStatus()constsw=counts.find((c:StatusCount)=>c.status==='待换绑')?.count??0constca=counts.find((c:StatusCount)=>c.status==='待注销')?.count??0constdi=counts.find((c:StatusCount)=>c.status==='已停用')?.count??0// 使用 animateTo 触发数字动效animateTo({duration:400,curve:Curve.EaseOut},()=>{this.pendingSwitch=swthis.pendingCancel=cathis.disabledCount=di})}catch(e){console.error('loadData failed:',JSON.stringify(e))}}5.3 animateTo 参数
animateTo({duration:400,// 动画时长(毫秒)curve:Curve.EaseOut,// 缓动曲线(EaseOut = 先快后慢)delay:0,// 延迟启动(毫秒)iterations:1,// 循环次数(-1 = 无限循环)playMode:PlayMode.Normal},()=>{// 在这里修改 @State 变量 → ArkUI 自动产生动画过渡this.pendingSwitch=newValue})5.4 常用 Curve 对比
| Curve | 特点 | 适用场景 |
|---|---|---|
Curve.Linear | 匀速 | 进度条 |
Curve.EaseIn | 先慢后快 | 元素飞入 |
Curve.EaseOut | 先快后慢 | 数字展示 ✅ |
Curve.EaseInOut | 两端慢中间快 | 通用过渡 |
Curve.Spring | 弹簧效果 | 元素弹出 |
六、点击跳转到状态列表
6.1 路由参数类型声明
// ArkTS 要求对象字面量有类型声明(不能是裸对象)interfaceStatusRouteParams{status:string}// 正确的跳转写法.onClick(()=>{constparams:StatusRouteParams={status:status}// 有类型声明this.navigate('pages/StatusListPage',params)})// ❌ 错误(ArkTS 编译报错):.onClick(()=>{this.navigate('pages/StatusListPage',{status:status})// ArkTS 2.0: "Object literal must correspond to some declared class or interface"})6.2 接收端读取参数
// StatusListPage.ets — 接收参数@Entry@Componentstruct StatusListPage{privatestatus:string=''onPageShow():void{constparams=router.getParams()asStatusRouteParamsif(params?.status){this.status=params.status}}}七、完整数据加载流程
// HomePage.ets — 完整 loadDataprivateasyncloadData():Promise<void>{if(this.rows.length===0){this.loading=true// 首次加载显示骨架}try{// ① 加载卡号列表(用于构建 AppRow)this.cards=awaitCardDao.listAll()constcardLabelMap=newMap<number,string>()for(constcofthis.cards){cardLabelMap.set(c.id??0,c.label)}// ② 加载各状态数量 → 更新统计卡数字constcounts:StatusCount[]=awaitAppBindingDao.countByStatus()animateTo({duration:400,curve:Curve.EaseOut},()=>{this.pendingSwitch=counts.find((c:StatusCount)=>c.status==='待换绑')?.count??0this.pendingCancel=counts.find((c:StatusCount)=>c.status==='待注销')?.count??0this.disabledCount=counts.find((c:StatusCount)=>c.status==='已停用')?.count??0})// ③ 加载所有绑定记录 → 构建 AppRow(含卡号标签)constbindings=awaitAppBindingDao.listAll()this.rows=bindings.map((b:AppBindingEntity):AppRow=>({binding:b,cardLabel:cardLabelMap.get(b.card_id)??''}))}catch(e){console.error('loadData failed:',JSON.stringify(e))}finally{this.loading=false}}八、本篇小结
| 知识点 | 核心要点 |
|---|---|
| StatCard @Builder | 参数化设计:icon/num/label/bg/fg/status 6 个参数 |
| 颜色语义 | WARN=黄/DANGER=红/MUTED=灰,背景色加 Alpha |
| #AARRGGBB | ArkUI 颜色格式,Alpha 在最前(不同于 CSS) |
| layoutWeight(1) | 三卡等分宽度,自适应屏幕 |
| animateTo | 数字变化时平滑动效,curve: EaseOut |
| StatusRouteParams | ArkTS 要求对象字面量有 interface 声明 |
参考资料
- 鸿蒙应用开发实战【14】— 毛玻璃卡片颜色格式深度解析
- 鸿蒙应用开发实战【05】— 首页开发与状态管理
- 鸿蒙应用开发实战【20】— 批量操作栏UI选择模式实现
- animateTo 动画 API
- Curve 缓动曲线枚举
- @Builder 装饰器参考
- layoutWeight 属性
- router 路由参数传递
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS animateTo 文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-explicit-animation
- AppColors 设计令牌:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-resource-access