39.[HarmonyOS NEXT Row案例七] 打造精美商品列表项:图文混排与多行文本的艺术
项目已开源,开源地址: 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组件在多行文本排列中的应用
- 弹性布局在商品列表项中的应用
- 多行文本的实现原理和截断处理
- 商品列表项的样式优化技巧
- 商品列表项的交互优化方法
- 商品列表项的扩展功能
- 商品列表项组件的封装与复用
掌握这些知识点后,你可以设计出美观、实用的商品列表项,提升应用的用户体验。在实际开发中,可以根据具体需求调整商品列表项的样式、布局和功能,创建符合应用设计风格的商品展示界面。
- 0回答
- 5粉丝
- 0关注
- 42.[HarmonyOS NEXT Row案例十] 精美图文混排卡片:左图标与右文本的完美结合
- 36.[HarmonyOS NEXT Row案例四] 打造精美图标按钮:垂直对齐与视觉平衡的艺术
- 41. [HarmonyOS NEXT Row案例九] 打造流畅可滑动列表项:滑动操作按钮的高级实现
- 181.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局基础篇:打造精美商品展示页面
- 157.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局:打造精美电商商品列表
- 38.[HarmonyOS NEXT Row案例六] 打造流畅水平滚动标签列表:Row与Scroll的完美结合
- 34. [HarmonyOS NEXT Row案例二] 打造响应式图文导航项:设备适配与弹性空间的完美结合
- 137.[HarmonyOS NEXT 实战案例七:List系列] 多列列表组件实战:打造精美应用推荐页 基础篇
- 138.[HarmonyOS NEXT 实战案例七:List系列] 多列列表组件实战:打造精美应用推荐页 进阶篇
- 135.[HarmonyOS NEXT 实战案例七:List系列] 水平列表组件实战:打造精美图片库 基础篇
- 136.[HarmonyOS NEXT 实战案例七:List系列] 水平列表组件实战:打造精美图片库 进阶篇
- 39. HarmonyOS NEXT Layout布局组件系统详解(六):偏移功能实现
- 151.[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 基础篇
- 152.[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 进阶篇
- 13.HarmonyOS NEXT流式卡片列表实现指南:Flex多行布局详解

