39.[HarmonyOS NEXT Row案例七] 打造精美商品列表项:图文混排与多行文本的艺术

2025-06-02 21:44:12
116次阅读
0个评论

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

img_26ba30fe.png

1. 概述

在电商应用和内容展示类应用中,商品列表项是一个核心UI组件,它需要在有限的空间内高效展示商品图片、标题、价格等多种信息。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件结合Column组件创建一个精美的商品列表项,实现图文混排与多行文本的完美展示。

2. 商品列表项的设计原则

在设计商品列表项时,需要考虑以下几个关键原则:

  1. 信息层级:突出重要信息(如商品名称、价格),次要信息(如描述、评分)可以用较小字体或浅色显示。
  2. 空间利用:合理利用有限空间,确保关键信息完整显示。
  3. 视觉平衡:图片与文本区域的比例要协调,通常图片占据固定宽度,文本区域自适应。
  4. 一致性:在整个应用中保持商品列表项的一致样式,提升用户体验。
  5. 可点击区域:整个列表项通常是可点击的,要确保点击区域足够大,便于用户操作。

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组件的maxLinestextOverflow属性实现。

5.1 maxLines属性

maxLines属性指定了Text组件最多显示的行数:

.maxLines(2) // 最多显示2行

当文本内容超过指定的行数时,超出部分会被截断。

5.2 textOverflow属性

textOverflow属性指定了文本溢出时的处理方式:

.textOverflow({ overflow: TextOverflow.Ellipsis }) // 溢出时显示省略号

TextOverflow.Ellipsis表示当文本溢出时显示省略号,这是一种常见的处理方式,能够提示用户还有更多内容未显示。

5.3 多行文本的布局考虑

在设计多行文本时,需要考虑以下几点:

  1. 行高:适当的行高可以提升多行文本的可读性。
  2. 字体大小:较小的字体可以在有限空间内显示更多内容。
  3. 截断方式:根据内容的重要性选择合适的截断方式。
  4. 交互方式:考虑是否需要提供"查看更多"的交互方式。

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组件创建精美的商品列表项,实现图文混排与多行文本的完美展示。通过本案例,我们学习了:

  1. 商品列表项的设计原则
  2. Row组件在图文混排中的应用
  3. Column组件在多行文本排列中的应用
  4. 弹性布局在商品列表项中的应用
  5. 多行文本的实现原理和截断处理
  6. 商品列表项的样式优化技巧
  7. 商品列表项的交互优化方法
  8. 商品列表项的扩展功能
  9. 商品列表项组件的封装与复用

掌握这些知识点后,你可以设计出美观、实用的商品列表项,提升应用的用户体验。在实际开发中,可以根据具体需求调整商品列表项的样式、布局和功能,创建符合应用设计风格的商品展示界面。

收藏00

登录 后评论。没有帐号? 注册 一个。