项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
在电商应用和内容展示类应用中,商品列表项是一个核心UI组件,它需要在有限的空间内高效展示商品图片、标题、价格等多种信息。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件结合Column组件创建一个精美的商品列表项,实现图文混排与多行文本的完美展示。
2. 商品列表项的设计原则
在设计商品列表项时,需要考虑以下几个关键原则:
- 信息层级:突出重要信息(如商品名称、价格),次要信息(如描述、评分)可以用较小字体或浅色显示。
- 空间利用:合理利用有限空间,确保关键信息完整显示。
- 视觉平衡:图片与文本区域的比例要协调,通常图片占据固定宽度,文本区域自适应。
- 一致性:在整个应用中保持商品列表项的一致样式,提升用户体验。
- 可点击区域:整个列表项通常是可点击的,要确保点击区域足够大,便于用户操作。
3. 案例分析:商品列表项
本案例展示了如何创建一个包含商品图片、标题、描述和价格的商品列表项,通过Row组件实现图文混排,通过Column组件实现多行文本排列。
3.1 完整代码
@Component export struct ProductItem { private productName: string = '超级好用的智能手表' private description: string = '这是一款功能强大的智能手表,支持心率监测、血氧检测、睡眠分析等多种健康功能,同时还具备防水、长续航等特点。' private price: string = '¥1299' build() { Row() { // 商品图片 Image($r('app.media.product_image')) .width(100) .height(100) .objectFit(ImageFit.Cover) .borderRadius(8) // 商品信息 Column() { Text(this.productName) .fontSize(16) .fontWeight(FontWeight.Bold) .margin({ bottom: 4 }) Text(this.description) .fontSize(14) .fontColor('#666666') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ bottom: 8 }) Text(this.price) .fontSize(16) .fontColor('#FF6B00') .fontWeight(FontWeight.Bold) } .alignItems(HorizontalAlign.Start) .flexGrow(1) .margin({ left: 12 }) } .width('100%') .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16) } }
3.2 代码详解
3.2.1 组件声明与数据定义
@Component export struct ProductItem { private productName: string = '超级好用的智能手表' private description: string = '这是一款功能强大的智能手表,支持心率监测、血氧检测、睡眠分析等多种健康功能,同时还具备防水、长续航等特点。' private price: string = '¥1299'
这部分代码声明了一个名为ProductItem
的自定义组件,并定义了三个私有属性:
属性 |
类型 |
说明 |
productName |
string |
商品名称 |
description |
string |
商品描述 |
price |
string |
商品价格 |
在实际应用中,这些数据通常会通过组件的构造参数传入,而不是硬编码在组件内部。
3.2.2 外层Row容器设置
Row() { // 子组件 } .width('100%') .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16)
这部分代码创建了一个Row容器,作为商品列表项的根容器。Row容器的属性设置如下:
属性 |
值 |
说明 |
width |
'100%' |
容器宽度为父容器的100% |
backgroundColor |
'#FFFFFF' |
设置背景色为白色 |
borderRadius |
12 |
设置边框圆角为12vp |
padding |
16 |
设置内边距为16vp |
这些设置确保了商品列表项有足够的空间和适当的内边距,白色背景和圆角边框使列表项在视觉上更加突出。
3.2.3 商品图片设置
Image($r('app.media.product_image')) .width(100) .height(100) .objectFit(ImageFit.Cover) .borderRadius(8)
这部分代码创建了一个Image组件,显示商品图片。Image组件的属性设置如下:
属性 |
值 |
说明 |
资源 |
$r('app.media.product_image') |
使用应用资源中的图片 |
width |
100 |
设置宽度为100vp |
height |
100 |
设置高度为100vp |
objectFit |
ImageFit.Cover |
设置图片填充模式为覆盖,保持宽高比 |
borderRadius |
8 |
设置边框圆角为8vp |
这些设置确保了商品图片有固定的尺寸和适当的圆角,ImageFit.Cover
模式确保图片能够完全覆盖指定区域,同时保持原始宽高比,避免图片变形。
3.2.4 商品信息Column容器设置
Column() { // 子组件 } .alignItems(HorizontalAlign.Start) .flexGrow(1) .margin({ left: 12 })
这部分代码创建了一个Column容器,用于垂直排列商品信息。Column容器的属性设置如下:
属性 |
值 |
说明 |
alignItems |
HorizontalAlign.Start |
子组件在水平方向上左对齐 |
flexGrow |
1 |
设置弹性增长系数为1,占据Row容器中的剩余空间 |
margin |
{ left: 12 } |
设置左侧外边距为12vp,与商品图片保持间距 |
这些设置确保了商品信息区域能够自适应占据除图片外的所有空间,并且内部的文本左对齐,与图片保持适当的间距。
3.2.5 商品名称设置
Text(this.productName) .fontSize(16) .fontWeight(FontWeight.Bold) .margin({ bottom: 4 })
这部分代码创建了一个Text组件,显示商品名称。Text组件的属性设置如下:
属性 |
值 |
说明 |
fontSize |
16 |
设置字体大小为16fp |
fontWeight |
FontWeight.Bold |
设置字体粗细为粗体 |
margin |
{ bottom: 4 } |
设置底部外边距为4vp |
这些设置使商品名称以粗体显示,突出其重要性,并与下方的商品描述保持适当的间距。
3.2.6 商品描述设置
Text(this.description) .fontSize(14) .fontColor('#666666') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ bottom: 8 })
这部分代码创建了一个Text组件,显示商品描述。Text组件的属性设置如下:
属性 |
值 |
说明 |
fontSize |
14 |
设置字体大小为14fp |
fontColor |
'#666666' |
设置字体颜色为深灰色 |
maxLines |
2 |
设置最大显示行数为2行 |
textOverflow |
{ overflow: TextOverflow.Ellipsis } |
设置文本溢出时显示省略号 |
margin |
{ bottom: 8 } |
设置底部外边距为8vp |
这些设置使商品描述以较小的字体和较浅的颜色显示,表明其次要地位,同时限制最大显示行数为2行,超出部分显示省略号,避免占用过多空间。
3.2.7 商品价格设置
Text(this.price) .fontSize(16) .fontColor('#FF6B00') .fontWeight(FontWeight.Bold)
这部分代码创建了一个Text组件,显示商品价格。Text组件的属性设置如下:
属性 |
值 |
说明 |
fontSize |
16 |
设置字体大小为16fp |
fontColor |
'#FF6B00' |
设置字体颜色为橙色 |
fontWeight |
FontWeight.Bold |
设置字体粗细为粗体 |
这些设置使商品价格以橙色粗体显示,突出其重要性,橙色是电商应用中常用的价格颜色,能够吸引用户注意。
4. 图文混排的实现原理
在HarmonyOS NEXT中,图文混排主要通过Row容器实现。Row容器是一个水平排列子组件的容器,可以将图片和文本区域放在同一行中,实现图文混排的效果。
4.1 Row容器的工作原理
Row容器默认将子组件水平排列,子组件之间的间距和对齐方式可以通过属性设置。在本案例中,Row容器包含两个子组件:一个Image组件和一个Column组件,它们被水平排列,形成图文混排的布局。
4.2 弹性布局的应用
在图文混排中,通常需要图片区域固定宽度,文本区域自适应占据剩余空间。这可以通过弹性布局实现:
Image(...).width(100) // 图片固定宽度 Column(...).flexGrow(1) // 文本区域自适应占据剩余空间
flexGrow
属性指定了弹性增长系数,当设置为1时,表示该组件会自动扩展以占据父容器中的剩余空间。在本案例中,Column容器的flexGrow
设置为1,使其能够自适应占据除图片外的所有空间。
5. 多行文本的实现原理
在商品列表项中,商品描述通常需要显示多行文本,并且在空间有限时需要截断显示。这可以通过Text组件的maxLines
和textOverflow
属性实现。
5.1 maxLines属性
maxLines
属性指定了Text组件最多显示的行数:
.maxLines(2) // 最多显示2行
当文本内容超过指定的行数时,超出部分会被截断。
5.2 textOverflow属性
textOverflow
属性指定了文本溢出时的处理方式:
.textOverflow({ overflow: TextOverflow.Ellipsis }) // 溢出时显示省略号
TextOverflow.Ellipsis
表示当文本溢出时显示省略号,这是一种常见的处理方式,能够提示用户还有更多内容未显示。
5.3 多行文本的布局考虑
在设计多行文本时,需要考虑以下几点:
- 行高:适当的行高可以提升多行文本的可读性。
- 字体大小:较小的字体可以在有限空间内显示更多内容。
- 截断方式:根据内容的重要性选择合适的截断方式。
- 交互方式:考虑是否需要提供"查看更多"的交互方式。
6. 商品列表项的样式优化
为了提升商品列表项的视觉效果和用户体验,我们可以进行以下优化:
6.1 阴影效果
添加阴影可以使商品列表项在视觉上更加突出:
.shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 })
这些设置创建了一个轻微的阴影效果,使商品列表项看起来像是悬浮在背景上,增强了层次感。
6.2 点击效果
添加点击效果可以提升用户交互体验:
.onClick(() => { // 处理点击事件 }) .stateStyles({ pressed: { opacity: 0.8, backgroundColor: '#F8F8F8' } })
这些设置使商品列表项在被点击时有明显的视觉反馈,提示用户点击操作已被识别。
6.3 边框效果
添加边框可以使商品列表项更加清晰:
.border({ width: 1, color: '#EEEEEE', radius: 12 })
这些设置添加了一个浅色边框,使商品列表项的边界更加清晰,特别是在白色背景上。
7. 商品列表项的交互优化
为了提升商品列表项的交互体验,我们可以添加以下功能:
7.1 收藏功能
在商品列表项中添加收藏按钮,使用户可以快速收藏感兴趣的商品:
Row() { // 商品图片和信息 // 收藏按钮 Column() { Image(this.isFavorite ? $r('app.media.ic_favorite_filled') : $r('app.media.ic_favorite')) .width(24) .height(24) .onClick((event) => { this.isFavorite = !this.isFavorite event.stopPropagation() // 阻止事件冒泡,避免触发列表项的点击事件 }) } .justifyContent(FlexAlign.Start) .alignSelf(ItemAlign.Start) }
在这个优化版本中,我们在Row容器的最右侧添加了一个收藏按钮,用户可以点击按钮收藏或取消收藏商品,同时阻止事件冒泡,避免触发列表项的点击事件。
7.2 数量选择
在商品列表项中添加数量选择功能,使用户可以直接调整购买数量:
Row() { // 商品图片和信息 // 价格和数量选择 Row() { Text(this.price) .fontSize(16) .fontColor('#FF6B00') .fontWeight(FontWeight.Bold) Blank() Row() { Button('-') .width(24) .height(24) .fontSize(14) .onClick((event) => { if (this.quantity > 1) { this.quantity-- } event.stopPropagation() }) Text(this.quantity.toString()) .width(36) .textAlign(TextAlign.Center) .fontSize(14) Button('+') .width(24) .height(24) .fontSize(14) .onClick((event) => { this.quantity++ event.stopPropagation() }) } .height(24) } .width('100%') }
在这个优化版本中,我们在价格旁边添加了数量选择功能,用户可以点击"+"或"-"按钮调整购买数量,同时阻止事件冒泡,避免触发列表项的点击事件。
8. 商品列表项的扩展功能
基于本案例的基本结构,我们可以扩展更多功能:
8.1 评分和评论数
在商品描述下方添加评分和评论数信息:
Row() { // 评分 Row() { ForEach([1, 2, 3, 4, 5], (item) => { Image(item <= this.rating ? $r('app.media.ic_star_filled') : $r('app.media.ic_star')) .width(16) .height(16) }) } Text(this.rating.toString()) .fontSize(14) .fontColor('#FF6B00') .margin({ left: 4 }) Text(`(${this.commentCount}条评论)`) .fontSize(14) .fontColor('#999999') .margin({ left: 8 }) } .margin({ bottom: 8 })
在这个扩展功能中,我们添加了评分和评论数信息,使用星星图标表示评分,并显示评论数量,提供更多商品信息。
8.2 标签和促销信息
在商品名称下方添加标签和促销信息:
Row({ space: 8 }) { Text('新品') .fontSize(12) .fontColor('#FF6B00') .backgroundColor('#FFF0E6') .borderRadius(4) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) Text('限时促销') .fontSize(12) .fontColor('#FF0000') .backgroundColor('#FFEBEB') .borderRadius(4) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) } .margin({ bottom: 8 })
在这个扩展功能中,我们添加了标签和促销信息,使用不同的背景色和文字颜色区分不同类型的信息,提供更多商品特点和促销信息。
9. 商品列表项组件的封装与复用
为了提高代码复用性,可以将商品列表项封装为一个独立的组件:
@Component export struct ProductItem { @Prop productName: string @Prop description: string @Prop price: string @Prop imageUrl: string @Prop rating: number = 0 @Prop commentCount: number = 0 @State isFavorite: boolean = false @State quantity: number = 1 onItemClick?: (productName: string) => void build() { Row() { // 商品图片 Image(this.imageUrl) .width(100) .height(100) .objectFit(ImageFit.Cover) .borderRadius(8) // 商品信息 Column() { Text(this.productName) .fontSize(16) .fontWeight(FontWeight.Bold) .margin({ bottom: 4 }) Text(this.description) .fontSize(14) .fontColor('#666666') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ bottom: 8 }) // 评分和评论数 if (this.rating > 0) { Row() { // 评分 Row() { ForEach([1, 2, 3, 4, 5], (item) => { Image(item <= this.rating ? $r('app.media.ic_star_filled') : $r('app.media.ic_star')) .width(16) .height(16) }) } Text(this.rating.toString()) .fontSize(14) .fontColor('#FF6B00') .margin({ left: 4 }) Text(`(${this.commentCount}条评论)`) .fontSize(14) .fontColor('#999999') .margin({ left: 8 }) } .margin({ bottom: 8 }) } // 价格和收藏 Row() { Text(this.price) .fontSize(16) .fontColor('#FF6B00') .fontWeight(FontWeight.Bold) Blank() Image(this.isFavorite ? $r('app.media.ic_favorite_filled') : $r('app.media.ic_favorite')) .width(24) .height(24) .onClick((event) => { this.isFavorite = !this.isFavorite event.stopPropagation() }) } .width('100%') } .alignItems(HorizontalAlign.Start) .flexGrow(1) .margin({ left: 12 }) } .width('100%') .backgroundColor('#FFFFFF') .borderRadius(12) .padding(16) .shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 }) .onClick(() => { if (this.onItemClick) { this.onItemClick(this.productName) } }) .stateStyles({ pressed: { opacity: 0.8, backgroundColor: '#F8F8F8' } }) } }
然后在应用中使用这个组件:
@Entry @Component struct ProductListPage { @State products: Array<{ name: string, description: string, price: string, imageUrl: string, rating: number, commentCount: number }> = [ { name: '超级好用的智能手表', description: '这是一款功能强大的智能手表,支持心率监测、血氧检测、睡眠分析等多种健康功能,同时还具备防水、长续航等特点。', price: '¥1299', imageUrl: $r('app.media.product_image_1'), rating: 4.5, commentCount: 1234 }, { name: '高性能游戏笔记本电脑', description: '搭载最新处理器和显卡,提供卓越的游戏体验和工作效率。', price: '¥8999', imageUrl: $r('app.media.product_image_2'), rating: 4.8, commentCount: 567 } // 更多商品... ] build() { Column({ space: 12 }) { // 标题 Text('商品列表') .fontSize(20) .fontWeight(FontWeight.Bold) .padding({ left: 16, top: 16, bottom: 8 }) // 商品列表 List() { ForEach(this.products, (product) => { ListItem() { ProductItem({ productName: product.name, description: product.description, price: product.price, imageUrl: product.imageUrl, rating: product.rating, commentCount: product.commentCount, onItemClick: (productName) => { // 处理商品点击事件 console.info('点击商品:' + productName) } }) } .padding({ left: 16, right: 16, bottom: 8 }) }) } .width('100%') .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#F5F5F5') } }
10. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的Row组件结合Column组件创建精美的商品列表项,实现图文混排与多行文本的完美展示。通过本案例,我们学习了:
- 商品列表项的设计原则
- Row组件在图文混排中的应用
- Column组件在多行文本排列中的应用
- 弹性布局在商品列表项中的应用
- 多行文本的实现原理和截断处理
- 商品列表项的样式优化技巧
- 商品列表项的交互优化方法
- 商品列表项的扩展功能
- 商品列表项组件的封装与复用
掌握这些知识点后,你可以设计出美观、实用的商品列表项,提升应用的用户体验。在实际开发中,可以根据具体需求调整商品列表项的样式、布局和功能,创建符合应用设计风格的商品展示界面。