项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
在数据分析、金融、健康等应用中,数据统计卡片是展示关键指标和趋势的重要UI组件。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件创建一个专业的数据统计卡片,实现两端对齐布局和响应式图标,打造出既美观又实用的数据可视化界面。
2. 数据统计卡片的设计原则
在设计数据统计卡片时,需要考虑以下几个关键原则:
- 信息层级:突出核心数据,辅助信息(如趋势、单位等)可以用较小字体或浅色显示。
- 视觉平衡:卡片内部元素的排列要平衡,避免一侧过于拥挤或空旷。
- 色彩编码:使用颜色区分不同类型的数据或趋势(如上升用绿色,下降用红色)。
- 响应式设计:卡片应能适应不同屏幕尺寸,保持良好的可读性。
- 一致性:在整个应用中保持统计卡片的一致样式,提升用户体验。
3. 案例分析:数据统计卡片
本案例展示了如何创建一个包含图标、数值和趋势信息的数据统计卡片,通过Row组件实现两端对齐布局,通过条件渲染实现响应式图标。
3.1 完整代码
@Component export struct StatCardExample { @Prop title: string = '总销售额' @Prop value: string = '¥ 126,560' @Prop trend: number = 12.3 // 正数表示上升,负数表示下降 @Prop timeRange: string = '周同比' build() { Row() { // 左侧:图标和标题 Row({ space: 8 }) { Image($r('app.media.ic_stats')) .width(24) .height(24) .objectFit(ImageFit.Contain) Text(this.title) .fontSize(16) .fontColor('#333333') } // 右侧:数值和趋势 Column() { Text(this.value) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#333333') .margin({ bottom: 4 }) Row({ space: 4 }) { Text(this.timeRange) .fontSize(12) .fontColor('#999999') Text(`${this.trend > 0 ? '+' : ''}${this.trend}%`) .fontSize(12) .fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D') // 趋势图标 Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down')) .width(12) .height(12) .objectFit(ImageFit.Contain) } } .alignItems(HorizontalAlign.End) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16) .shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 }) } }
3.2 代码详解
3.2.1 组件声明与数据定义
@Component export struct StatCardExample { @Prop title: string = '总销售额' @Prop value: string = '¥ 126,560' @Prop trend: number = 12.3 // 正数表示上升,负数表示下降 @Prop timeRange: string = '周同比'
这部分代码声明了一个名为StatCardExample的自定义组件,并定义了四个属性:
属性 |
类型 |
说明 |
默认值 |
title |
string |
统计指标的标题 |
'总销售额' |
value |
string |
统计指标的数值 |
'¥ 126,560' |
trend |
number |
趋势变化百分比,正数表示上升,负数表示下降 |
12.3 |
timeRange |
string |
时间范围说明 |
'周同比' |
使用@Prop装饰器定义这些属性,使它们可以从外部传入,同时提供默认值。这种设计使组件更加灵活,可以用于展示不同类型的统计数据。
3.2.2 外层Row容器设置
Row() { // 子组件 } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16) .shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 })
这部分代码创建了一个Row容器,作为数据统计卡片的根容器。Row容器的属性设置如下:
属性 |
值 |
说明 |
width |
'100%' |
容器宽度为父容器的100% |
justifyContent |
FlexAlign.SpaceBetween |
子组件在主轴(水平方向)上两端对齐 |
backgroundColor |
'#FFFFFF' |
设置背景色为白色 |
borderRadius |
12 |
设置边框圆角为12vp |
padding |
16 |
设置内边距为16vp |
shadow |
{ radius: 6, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 } |
设置阴影效果 |
这些设置确保了数据统计卡片有足够的空间和适当的内边距,白色背景、圆角边框和阴影效果使卡片在视觉上更加突出。最关键的是justifyContent属性设置为FlexAlign.SpaceBetween,实现了左右两端对齐的布局。
3.2.3 左侧区域设置
Row({ space: 8 }) { Image($r('app.media.ic_stats')) .width(24) .height(24) .objectFit(ImageFit.Contain) Text(this.title) .fontSize(16) .fontColor('#333333') }
这部分代码创建了一个Row容器,用于水平排列左侧的图标和标题。Row容器的属性设置如下:
属性 |
值 |
说明 |
space |
8 |
子组件之间的间距为8vp |
内部包含两个子组件:
- Image组件:显示统计指标的图标
- width: 24 - 设置宽度为24vp
- height: 24 - 设置高度为24vp
- objectFit: ImageFit.Contain - 设置图片填充模式为包含,保持宽高比
- Text组件:显示统计指标的标题
- fontSize: 16 - 设置字体大小为16fp
- fontColor: '#333333' - 设置字体颜色为深灰色
这些设置确保了图标和标题有适当的大小和间距,形成一个紧凑的左侧区域。
3.2.4 右侧区域设置
Column() { Text(this.value) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#333333') .margin({ bottom: 4 }) Row({ space: 4 }) { Text(this.timeRange) .fontSize(12) .fontColor('#999999') Text(`${this.trend > 0 ? '+' : ''}${this.trend}%`) .fontSize(12) .fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D') // 趋势图标 Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down')) .width(12) .height(12) .objectFit(ImageFit.Contain) } } .alignItems(HorizontalAlign.End)
这部分代码创建了一个Column容器,用于垂直排列右侧的数值和趋势信息。Column容器的属性设置如下:
属性 |
值 |
说明 |
alignItems |
HorizontalAlign.End |
子组件在水平方向上右对齐 |
内部包含两个子组件:
- Text组件:显示统计指标的数值
- fontSize: 20 - 设置字体大小为20fp
- fontWeight: FontWeight.Bold - 设置字体粗细为粗体
- fontColor: '#333333' - 设置字体颜色为深灰色
- margin: { bottom: 4 } - 设置底部外边距为4vp
- Row容器:水平排列时间范围、趋势百分比和趋势图标
- space: 4 - 子组件之间的间距为4vp
- 内部包含三个子组件:
- Text组件:显示时间范围
- fontSize: 12 - 设置字体大小为12fp
- fontColor: '#999999' - 设置字体颜色为浅灰色
- Text组件:显示趋势百分比
- fontSize: 12 - 设置字体大小为12fp
- fontColor: 动态设置,上升趋势为绿色(#52C41A),下降趋势为红色(#F5222D)
- Image组件:显示趋势图标
- width: 12 - 设置宽度为12vp
- height: 12 - 设置高度为12vp
- objectFit: ImageFit.Contain - 设置图片填充模式为包含,保持宽高比
这些设置确保了数值和趋势信息有适当的大小和间距,形成一个紧凑的右侧区域,并且通过alignItems(HorizontalAlign.End)使内部元素右对齐。
4. 两端对齐布局的实现原理
在HarmonyOS NEXT中,两端对齐布局主要通过Row容器的justifyContent属性实现。
4.1 justifyContent属性
justifyContent属性控制子组件在主轴(对于Row容器来说是水平方向)上的对齐方式。当设置为FlexAlign.SpaceBetween时,子组件会分布在容器的两端,中间留有空白。
.justifyContent(FlexAlign.SpaceBetween)
FlexAlign枚举提供了多种对齐方式:
值 |
说明 |
Start |
子组件在主轴起点对齐 |
Center |
子组件在主轴中心对齐 |
End |
子组件在主轴终点对齐 |
SpaceBetween |
子组件在主轴上均匀分布,首尾子组件分别贴近容器两端 |
SpaceAround |
子组件在主轴上均匀分布,包括首尾子组件与容器两端的间距 |
SpaceEvenly |
子组件在主轴上均匀分布,所有间距相等 |
在本案例中,我们使用FlexAlign.SpaceBetween实现两端对齐,使左侧的图标和标题靠左显示,右侧的数值和趋势信息靠右显示,中间留有空白,形成视觉上的平衡。
4.2 两端对齐的应用场景
两端对齐布局在以下场景中特别有用:
- 标题和操作按钮:左侧显示标题,右侧显示操作按钮。
- 列表项的主要信息和次要信息:左侧显示主要信息,右侧显示次要信息或状态。
- 表单的标签和输入框:左侧显示标签,右侧显示输入框。
- 统计卡片的指标和数值:左侧显示指标名称,右侧显示数值。
在本案例中,我们使用两端对齐布局展示统计指标的标题和数值,使卡片内容更加清晰和有条理。
5. 响应式图标的实现原理
在HarmonyOS NEXT中,响应式图标主要通过条件渲染实现,根据数据的不同状态显示不同的图标。
5.1 条件渲染
条件渲染是指根据条件动态决定渲染什么内容。在本案例中,我们根据趋势值的正负显示不同的图标:
Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down'))
这行代码使用三元运算符根据this.trend的值决定显示上升趋势图标还是下降趋势图标:
- 当
this.trend >= 0时,显示上升趋势图标 - 当
this.trend < 0时,显示下降趋势图标
5.2 动态样式
除了图标本身,我们还可以根据数据状态动态设置样式,如文字颜色:
.fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D')
这行代码使用三元运算符根据this.trend的值决定文字颜色:
- 当
this.trend >= 0时,文字颜色为绿色(#52C41A) - 当
this.trend < 0时,文字颜色为红色(#F5222D)
通过这种方式,我们可以创建出响应数据变化的UI元素,提升用户体验。
5.3 响应式设计的优势
响应式设计有以下优势:
- 信息传达更直观:通过颜色和图标的变化,用户可以更快地理解数据的含义。
- 减少认知负担:用户不需要仔细阅读数字,就能通过视觉元素了解趋势。
- 提升用户体验:动态变化的UI元素使应用更加生动和有吸引力。
- 适应不同数据:同一组件可以适应不同的数据状态,提高代码复用性。
在本案例中,我们通过响应式图标和颜色,直观地表达了数据的上升或下降趋势,使用户能够更快地理解数据的含义。
6. 数据统计卡片的样式优化
为了提升数据统计卡片的视觉效果和用户体验,我们可以进行以下优化:
6.1 卡片阴影优化
适当的阴影可以增强卡片的层次感:
.shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 })
这些设置创建了一个轻微的阴影效果,使卡片看起来像是悬浮在背景上,增强了层次感。阴影的offsetY设置为正值,模拟了光源从上方照射的效果,符合用户的视觉习惯。
6.2 卡片边框优化
添加边框可以使卡片更加清晰:
.border({ width: 1, color: '#EEEEEE', radius: 12 })
这些设置添加了一个浅色边框,使卡片的边界更加清晰,特别是在白色背景上。
6.3 卡片内边距优化
适当的内边距可以使卡片内容更加舒适:
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
这些设置使卡片内容有足够的呼吸空间,不会显得拥挤。水平内边距通常大于垂直内边距,符合用户的阅读习惯。
6.4 文字大小和颜色优化
适当的文字大小和颜色可以突出重要信息:
// 数值 Text(this.value) .fontSize(24) // 增大字体 .fontWeight(FontWeight.Bold) .fontColor('#333333') .margin({ bottom: 4 }) // 趋势 Text(`${this.trend > 0 ? '+' : ''}${this.trend}%`) .fontSize(14) // 增大字体 .fontWeight(FontWeight.Medium) // 增加字重 .fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D')
这些设置使数值和趋势更加突出,便于用户快速获取关键信息。
7. 数据统计卡片的交互优化
为了提升数据统计卡片的交互体验,我们可以添加以下功能:
7.1 点击查看详情
添加点击事件,使用户可以查看详细数据:
.onClick(() => { // 处理点击事件,如跳转到详情页面 }) .stateStyles({ pressed: { opacity: 0.8, backgroundColor: '#F8F8F8' } })
这些设置使卡片在被点击时有明显的视觉反馈,提示用户点击操作已被识别。
7.2 长按查看说明
添加长按事件,显示指标的说明信息:
.gesture( LongPressGesture() .onAction(() => { // 显示指标说明 }) )
这些设置使用户可以通过长按卡片查看指标的详细说明,了解数据的含义和计算方式。
8. 数据统计卡片的扩展功能
基于本案例的基本结构,我们可以扩展更多功能:
8.1 历史趋势图表
在卡片中添加迷你趋势图表,直观展示数据的历史变化:
@Component struct MiniTrendChart { @Prop data: number[] = [] @Prop color: string = '#52C41A' build() { Row() { ForEach(this.data, (value, index) => { Column() { // 绘制柱状图 Column() .width(4) .height(value * 20) // 根据数值设置高度 .backgroundColor(this.color) .borderRadius(2) } .height(20) .alignItems(HorizontalAlign.Center) .justifyContent(FlexAlign.End) }) } .width(60) .height(20) .margin({ left: 8 }) } }
然后在数据统计卡片中使用这个组件:
Row({ space: 4 }) { Text(this.timeRange) .fontSize(12) .fontColor('#999999') Text(`${this.trend > 0 ? '+' : ''}${this.trend}%`) .fontSize(12) .fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D') // 趋势图标 Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down')) .width(12) .height(12) .objectFit(ImageFit.Contain) // 迷你趋势图表 MiniTrendChart({ data: [0.2, 0.5, 0.3, 0.8, 0.6], color: this.trend >= 0 ? '#52C41A' : '#F5222D' }) }
8.2 目标完成度
在卡片中添加目标完成度指示器,展示当前数值与目标的对比:
@Component struct ProgressIndicator { @Prop current: number = 0 @Prop target: number = 100 @Prop color: string = '#1890FF' build() { Column({ space: 4 }) { Row() { Text('目标完成度') .fontSize(12) .fontColor('#999999') Text(`${Math.round(this.current / this.target * 100)}%`) .fontSize(12) .fontColor(this.color) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) Row() { Row() .width(`${this.current / this.target * 100}%`) .height(4) .backgroundColor(this.color) .borderRadius(2) } .width('100%') .height(4) .backgroundColor('#F0F0F0') .borderRadius(2) } .width('100%') .margin({ top: 8 }) } }
然后在数据统计卡片中使用这个组件:
Column() { // 现有内容 // 目标完成度指示器 ProgressIndicator({ current: 126560, target: 200000, color: '#1890FF' }) }
9. 数据统计卡片组件的封装与复用
为了提高代码复用性,可以将数据统计卡片封装为一个独立的组件:
@Component export struct StatCard { @Prop title: string = '' @Prop icon: Resource = $r('app.media.ic_stats') @Prop value: string = '' @Prop trend: number = 0 @Prop timeRange: string = '' @Prop trendData?: number[] = undefined @Prop target?: number = undefined onCardClick?: () => void build() { Row() { // 左侧:图标和标题 Row({ space: 8 }) { Image(this.icon) .width(24) .height(24) .objectFit(ImageFit.Contain) Text(this.title) .fontSize(16) .fontColor('#333333') } // 右侧:数值和趋势 Column() { Text(this.value) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#333333') .margin({ bottom: 4 }) Row({ space: 4 }) { Text(this.timeRange) .fontSize(12) .fontColor('#999999') Text(`${this.trend > 0 ? '+' : ''}${this.trend}%`) .fontSize(12) .fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D') // 趋势图标 Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down')) .width(12) .height(12) .objectFit(ImageFit.Contain) // 迷你趋势图表(如果有数据) if (this.trendData) { MiniTrendChart({ data: this.trendData, color: this.trend >= 0 ? '#52C41A' : '#F5222D' }) } } // 目标完成度指示器(如果有目标) if (this.target) { ProgressIndicator({ current: parseFloat(this.value.replace(/[^\d.-]/g, '')), target: this.target, color: '#1890FF' }) } } .alignItems(HorizontalAlign.End) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16) .shadow({ radius: 6, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 }) .onClick(() => { if (this.onCardClick) { this.onCardClick() } }) .stateStyles({ pressed: { opacity: 0.8, backgroundColor: '#F8F8F8' } }) } }
然后在应用中使用这个组件:
@Entry @Component struct DashboardPage { @State stats: Array<{ title: string, icon: Resource, value: string, trend: number, timeRange: string, trendData?: number[], target?: number }> = [ { title: '总销售额', icon: $r('app.media.ic_sales'), value: '¥ 126,560', trend: 12.3, timeRange: '周同比', trendData: [0.2, 0.5, 0.3, 0.8, 0.6], target: 200000 }, { title: '访问量', icon: $r('app.media.ic_visits'), value: '8,846', trend: -2.5, timeRange: '日同比' }, { title: '支付笔数', icon: $r('app.media.ic_payments'), value: '6,560', trend: 3.1, timeRange: '周同比' } ] build() { Column({ space: 16 }) { // 标题 Text('数据概览') .fontSize(20) .fontWeight(FontWeight.Bold) .padding({ left: 16, top: 16, bottom: 8 }) // 统计卡片列表 ForEach(this.stats, (stat) => { StatCard({ title: stat.title, icon: stat.icon, value: stat.value, trend: stat.trend, timeRange: stat.timeRange, trendData: stat.trendData, target: stat.target, onCardClick: () => { // 处理卡片点击事件 console.info('点击卡片:' + stat.title) } }) .margin({ left: 16, right: 16 }) }) } .width('100%') .height('100%') .backgroundColor('#F5F5F5') } }
10. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的Row组件创建专业的数据统计卡片,实现两端对齐布局和响应式图标。通过本案例,我们学习了:
- 数据统计卡片的设计原则
- Row组件在两端对齐布局中的应用
- 条件渲染在响应式图标中的应用
- 动态样式在数据可视化中的应用
- 数据统计卡片的样式优化技巧
- 数据统计卡片的交互优化方法
- 数据统计卡片的扩展功能
- 数据统计卡片组件的封装与复用
掌握这些知识点后,你可以设计出美观、实用的数据统计卡片,提升应用的数据可视化能力和用户体验。在实际开发中,可以根据具体需求调整卡片的样式、布局和功能,创建符合应用设计风格的数据展示界面。