项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
在数据分析、金融、健康等应用中,数据统计卡片是展示关键指标和趋势的重要UI组件。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件创建一个专业的数据统计卡片,实现两端对齐布局和响应式图标,打造出既美观又实用的数据可视化界面。
在设计数据统计卡片时,需要考虑以下几个关键原则:
本案例展示了如何创建一个包含图标、数值和趋势信息的数据统计卡片,通过Row组件实现两端对齐布局,通过条件渲染实现响应式图标。
@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
})
}
}
@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
装饰器定义这些属性,使它们可以从外部传入,同时提供默认值。这种设计使组件更加灵活,可以用于展示不同类型的统计数据。
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
,实现了左右两端对齐的布局。
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 |
内部包含两个子组件:
这些设置确保了图标和标题有适当的大小和间距,形成一个紧凑的左侧区域。
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 | 子组件在水平方向上右对齐 |
内部包含两个子组件:
这些设置确保了数值和趋势信息有适当的大小和间距,形成一个紧凑的右侧区域,并且通过alignItems(HorizontalAlign.End)
使内部元素右对齐。
在HarmonyOS NEXT中,两端对齐布局主要通过Row容器的justifyContent
属性实现。
justifyContent
属性控制子组件在主轴(对于Row容器来说是水平方向)上的对齐方式。当设置为FlexAlign.SpaceBetween
时,子组件会分布在容器的两端,中间留有空白。
.justifyContent(FlexAlign.SpaceBetween)
FlexAlign
枚举提供了多种对齐方式:
值 | 说明 |
---|---|
Start | 子组件在主轴起点对齐 |
Center | 子组件在主轴中心对齐 |
End | 子组件在主轴终点对齐 |
SpaceBetween | 子组件在主轴上均匀分布,首尾子组件分别贴近容器两端 |
SpaceAround | 子组件在主轴上均匀分布,包括首尾子组件与容器两端的间距 |
SpaceEvenly | 子组件在主轴上均匀分布,所有间距相等 |
在本案例中,我们使用FlexAlign.SpaceBetween
实现两端对齐,使左侧的图标和标题靠左显示,右侧的数值和趋势信息靠右显示,中间留有空白,形成视觉上的平衡。
两端对齐布局在以下场景中特别有用:
在本案例中,我们使用两端对齐布局展示统计指标的标题和数值,使卡片内容更加清晰和有条理。
在HarmonyOS NEXT中,响应式图标主要通过条件渲染实现,根据数据的不同状态显示不同的图标。
条件渲染是指根据条件动态决定渲染什么内容。在本案例中,我们根据趋势值的正负显示不同的图标:
Image(this.trend >= 0 ? $r('app.media.ic_trend_up') : $r('app.media.ic_trend_down'))
这行代码使用三元运算符根据this.trend
的值决定显示上升趋势图标还是下降趋势图标:
this.trend >= 0
时,显示上升趋势图标this.trend < 0
时,显示下降趋势图标除了图标本身,我们还可以根据数据状态动态设置样式,如文字颜色:
.fontColor(this.trend >= 0 ? '#52C41A' : '#F5222D')
这行代码使用三元运算符根据this.trend
的值决定文字颜色:
this.trend >= 0
时,文字颜色为绿色(#52C41A)this.trend < 0
时,文字颜色为红色(#F5222D)通过这种方式,我们可以创建出响应数据变化的UI元素,提升用户体验。
响应式设计有以下优势:
在本案例中,我们通过响应式图标和颜色,直观地表达了数据的上升或下降趋势,使用户能够更快地理解数据的含义。
为了提升数据统计卡片的视觉效果和用户体验,我们可以进行以下优化:
适当的阴影可以增强卡片的层次感:
.shadow({
radius: 6,
color: 'rgba(0, 0, 0, 0.1)',
offsetX: 0,
offsetY: 2
})
这些设置创建了一个轻微的阴影效果,使卡片看起来像是悬浮在背景上,增强了层次感。阴影的offsetY
设置为正值,模拟了光源从上方照射的效果,符合用户的视觉习惯。
添加边框可以使卡片更加清晰:
.border({
width: 1,
color: '#EEEEEE',
radius: 12
})
这些设置添加了一个浅色边框,使卡片的边界更加清晰,特别是在白色背景上。
适当的内边距可以使卡片内容更加舒适:
.padding({
left: 16,
right: 16,
top: 12,
bottom: 12
})
这些设置使卡片内容有足够的呼吸空间,不会显得拥挤。水平内边距通常大于垂直内边距,符合用户的阅读习惯。
适当的文字大小和颜色可以突出重要信息:
// 数值
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')
这些设置使数值和趋势更加突出,便于用户快速获取关键信息。
为了提升数据统计卡片的交互体验,我们可以添加以下功能:
添加点击事件,使用户可以查看详细数据:
.onClick(() => {
// 处理点击事件,如跳转到详情页面
})
.stateStyles({
pressed: {
opacity: 0.8,
backgroundColor: '#F8F8F8'
}
})
这些设置使卡片在被点击时有明显的视觉反馈,提示用户点击操作已被识别。
添加长按事件,显示指标的说明信息:
.gesture(
LongPressGesture()
.onAction(() => {
// 显示指标说明
})
)
这些设置使用户可以通过长按卡片查看指标的详细说明,了解数据的含义和计算方式。
基于本案例的基本结构,我们可以扩展更多功能:
在卡片中添加迷你趋势图表,直观展示数据的历史变化:
@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'
})
}
在卡片中添加目标完成度指示器,展示当前数值与目标的对比:
@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'
})
}
为了提高代码复用性,可以将数据统计卡片封装为一个独立的组件:
@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')
}
}
本教程详细讲解了如何使用HarmonyOS NEXT的Row组件创建专业的数据统计卡片,实现两端对齐布局和响应式图标。通过本案例,我们学习了:
掌握这些知识点后,你可以设计出美观、实用的数据统计卡片,提升应用的数据可视化能力和用户体验。在实际开发中,可以根据具体需求调整卡片的样式、布局和功能,创建符合应用设计风格的数据展示界面。