在这里插入图片描述

每日一句正能量

“不要因为自己的工作已经完成了99%,就觉得可以放松了,真正决定成败的往往是那最后的1%。”
99%是产品,最后的1%是作品;99%是及格,最后的1%是惊艳。就像烧水,烧到99度水还没开,只差那1度,前面的能量都白费了。最后的收尾工作、交付时的细节检查,往往决定了你在别人心中是“靠谱”还是“将就”。

摘要

摘要:在电商、外卖、出行、内容社区等各类应用中,评分系统是连接用户反馈与平台优化的核心纽带。HarmonyOS ArkUI 框架提供的 Rating(评分) 组件,以轻量化的接口设计、灵活的自定义能力和完善的交互事件体系,为开发者构建专业级评分功能提供了官方标准方案。本文将从组件架构、核心 API、基础用法、进阶实战以及自定义扩展等多个维度,系统讲解 Rating 组件的完整开发方案,帮助开发者快速构建兼具美观与实用的评分系统。


一、引言:评分组件在现代应用中的价值

评分系统是现代移动应用不可或缺的基础设施之一。无论是用户购买商品后的满意度评价、骑手配送服务的服务质量打分,还是应用商店中用户对 App 的星级评定,评分组件都承担着量化用户反馈、辅助决策判断、驱动产品迭代的关键角色。

一个优秀的评分组件需要满足以下核心诉求:

  • 视觉直观:通过星级、进度条等可视化形式,让用户一眼感知评分高低
  • 交互流畅:支持点击、滑动等多种交互方式,评分过程自然无阻塞
  • 精度可控:支持整星、半星甚至更小粒度的评分精度,满足不同业务场景
  • 样式可定制:支持自定义图标、颜色、尺寸,与品牌视觉体系保持一致
  • 模式灵活:既能作为交互式输入组件,也能作为只读展示指示器

HarmonyOS 的 Rating 组件API Version 7 开始提供支持,在 API 9 之后全面支持 ArkTS 卡片场景。经过多个版本的迭代,该组件在功能完备性、性能表现和可扩展性方面均已达到生产级标准,是鸿蒙生态中构建评分系统的首选官方组件


二、Rating 组件架构与核心概念

在深入代码之前,我们先从架构层面理解 Rating 组件的设计模型。

在这里插入图片描述

2.1 组件接口设计

Rating 组件采用极简的接口设计,仅需两个核心参数即可完成初始化:

Rating(options?: { rating: number, indicator?: boolean })
  • rating(必填):当前评分值,取值范围为 [0, stars]。小于 0 时按 0 处理,大于 stars 时按最大值处理。该参数支持双向绑定,当用户交互改变评分时,绑定的状态变量会自动同步更新。
  • indicator(可选):控制组件的工作模式。false(默认)表示可交互评分模式,用户可点击或滑动改变评分;true 表示只读指示器模式,仅用于展示评分结果,不可交互。

这种"一个组件、两种模式"的设计,使得开发者无需引入额外的展示组件,仅凭一个 Rating 即可覆盖评分输入和评分展示两种场景,显著降低了代码复杂度和维护成本。

2.2 核心属性体系

Rating 组件提供了三个关键属性,用于精细控制评分行为与视觉表现:

属性 类型 默认值 说明
stars number 5 设置评分总数,支持任意正整数。常见取值有 5(五星制)和 10(十分制)
stepSize number 0.5 评分步长,控制用户每次交互的最小评分变化量。取值范围为 [0.1, stars]
starStyle StarStyle 系统默认 自定义星级图片,包含 backgroundUri(未选中)、foregroundUri(选中)、secondaryUri(部分选中)三个字段

2.3 星级渲染的三态模型

Rating 组件在渲染时,根据当前评分值与每个星级的位置关系,将每个星级渲染为三种状态之一:

  • 完全选中(foreground):当评分值覆盖整个星级区域时,使用前景图片渲染
  • 部分选中(secondary):当评分值仅覆盖星级区域的一部分时(如半星场景),使用次级图片渲染左侧,背景图片渲染右侧
  • 未选中(background):当评分值未覆盖该星级时,使用背景图片渲染

三态模型是实现半星、四分之一星等精细化评分展示的核心机制。开发者通过提供对应的三套图片资源,即可实现完全自定义的视觉风格。


三、核心 API 与事件机制详解

3.1 尺寸与布局特性

Rating 组件的尺寸行为在不同模式下存在差异,这是开发中需要特别注意的细节:

  • indicator = true(只读模式):默认组件高度 height = 12.0vp,组件宽度 width = height × stars。整体呈现紧凑的迷你尺寸,适合嵌入列表项、卡片等空间受限的场景。
  • indicator = false(交互模式):默认组件高度 height = 28.0vp,组件宽度同样为 width = height × stars。更大的触摸区域确保了用户交互的准确性。

当开发者自定义宽高时,单个星级的绘制区域为 [width / stars, height]。为保证星级显示为正方形,建议采用 width = height × stars 的等比设置方式。

3.2 onChange 事件回调

Rating 组件仅提供一个事件回调 onChange,在用户通过点击或滑动改变评分值时触发:

.onChange((value: number) => {
  // value 为当前评分值
  this.rating = value
})

虽然事件接口简洁,但配合 ArkTS 的状态管理机制,已足以覆盖绝大多数业务场景。对于需要监听评分开始、评分结束等更细粒度事件的场景,可以通过外层容器的手势系统(PanGestureTapGesture)进行扩展实现。

3.3 自定义图片规范

starStyle 属性是 Rating 组件实现品牌视觉定制的关键。其类型定义为:

interface StarStyle {
  backgroundUri: string   // 未选中状态的图片路径
  foregroundUri: string   // 完全选中状态的图片路径
  secondaryUri: string    // 部分选中状态的图片路径
}

使用自定义图片时需注意以下规范:

  1. 三张图片必须同时设置,若任一字段为 undefined 或空字符串,组件将回退到系统默认的灰色星形图源
  2. 图片格式支持:png、jpg 等常见格式,支持本地路径和网络图片 URL
  3. 暂不支持 PixelMap 类型和 Resource 资源类型,图片加载方式为异步
  4. 图片尺寸建议:为保证渲染清晰度,建议提供与目标显示尺寸匹配或更高分辨率的图片资源

在这里插入图片描述


四、基础用法:快速上手 Rating

4.1 最简单的五星评分

下面展示 Rating 组件最基础的使用方式——一个可交互的五星评分条:

// RatingBasicDemo.ets
@Entry
@Component
struct RatingBasicDemo {
  @State currentRating: number = 3.5

  build() {
    Column({ space: 24 }) {
      Text('请为本次服务评分')
        .fontSize(20)
        .fontWeight(FontWeight.Medium)
        .fontColor('#182431')

      Rating({ rating: this.currentRating, indicator: false })
        .stars(5)
        .stepSize(0.5)
        .margin({ top: 16 })
        .onChange((value: number) => {
          this.currentRating = value
          console.info(`用户评分: ${value}`)
        })

      Text(`当前评分: ${this.currentRating}`)
        .fontSize(16)
        .fontColor('rgba(24,36,49,0.60)')
        .margin({ top: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
    .justifyContent(FlexAlign.Center)
  }
}

代码要点解析

  1. 双向绑定rating: this.currentRating 将组件状态与 @State 变量绑定,用户交互后评分值自动同步到状态变量
  2. 步长控制stepSize(0.5) 设置最小评分单位为半星,用户每次点击或滑动至少改变 0.5 分
  3. 事件响应onChange 回调接收最新的评分值,可用于表单收集、网络请求等后续操作

4.2 只读展示模式

当 Rating 用于展示商品评分、服务评分等已有数据时,应设置为指示器模式:

Rating({ rating: 4.2, indicator: true })
  .stars(5)
  .stepSize(0.1)

指示器模式下,组件尺寸自动缩小至 12.0vp 高度,适合嵌入列表项、评价卡片等场景。stepSize(0.1) 的设置确保了评分展示精度,即使后端返回 4.2 这样的小数评分,也能准确渲染。


五、进阶实战:商品评价系统完整实现

基础用法只能应对单一评分场景,在实际业务中,评分系统往往需要与多维评价、评论列表、评分统计等模块深度整合。下面以电商商品评价系统为例,展示一个生产级的 Rating 综合应用方案。

在这里插入图片描述

5.1 数据模型设计

首先定义评价系统的数据模型:

// models/RatingModel.ets

export interface RatingDimension {
  name: string        // 维度名称,如"商品质量"
  score: number      // 维度评分
  maxScore: number   // 维度满分,通常为5
}

export interface ReviewItem {
  userId: string
  userName: string
  avatar: string
  rating: number
  content: string
  createTime: string
  images?: string[]
}

export interface ProductRating {
  overallScore: number
  totalCount: number
  dimensions: RatingDimension[]
  reviews: ReviewItem[]
}

export const MOCK_PRODUCT_RATING: ProductRating = {
  overallScore: 4.8,
  totalCount: 2847,
  dimensions: [
    { name: '商品质量', score: 4.9, maxScore: 5 },
    { name: '物流服务', score: 4.7, maxScore: 5 },
    { name: '客服态度', score: 4.8, maxScore: 5 }
  ],
  reviews: [
    {
      userId: 'u001',
      userName: '用户_9527',
      avatar: '',
      rating: 5.0,
      content: '商品质量很好,物流速度快,非常满意这次购物体验!',
      createTime: '2026-08-01'
    }
  ]
}

5.2 评价页面完整实现

// pages/ProductRatingPage.ets
import { ProductRating, ReviewItem, MOCK_PRODUCT_RATING } from '../models/RatingModel'

@Entry
@Component
struct ProductRatingPage {
  @State productRating: ProductRating = MOCK_PRODUCT_RATING
  @State isWritingReview: boolean = false
  @State userRating: number = 5.0
  @State userComment: string = ''

  @Builder
  OverallRatingSection() {
    Column({ space: 8 }) {
      Row({ space: 16 }) {
        Column({ space: 4 }) {
          Text(this.productRating.overallScore.toFixed(1))
            .fontSize(36)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFB300')
          Text('综合评分')
            .fontSize(12)
            .fontColor('#999')
        }
        .alignItems(HorizontalAlign.Start)

        Column({ space: 4 }) {
          Rating({ rating: this.productRating.overallScore, indicator: true })
            .stars(5)
            .stepSize(0.1)
          Text(`基于 ${this.productRating.totalCount} 条评价`)
            .fontSize(12)
            .fontColor('#999')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
      .borderRadius(12)
    }
  }

  @Builder
  DimensionRatingSection() {
    Column({ space: 12 }) {
      Text('分项评分')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#182431')
        .width('100%')

      ForEach(this.productRating.dimensions, (dim: RatingDimension) => {
        Row({ space: 12 }) {
          Text(dim.name)
            .fontSize(14)
            .fontColor('#666')
            .width(70)

          // 使用 Rating 作为只读指示器
          Rating({ rating: dim.score, indicator: true })
            .stars(dim.maxScore)
            .stepSize(0.1)
            .height(14)

          Text(dim.score.toFixed(1))
            .fontSize(14)
            .fontColor('#4CAF50')
            .fontWeight(FontWeight.Medium)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      })
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }

  @Builder
  ReviewItemCard(review: ReviewItem) {
    Column({ space: 8 }) {
      Row({ space: 8 }) {
        Circle()
          .width(32)
          .height(32)
          .fill('#BBDEFB')
        Text(review.userName)
          .fontSize(14)
          .fontColor('#182431')
          .layoutWeight(1)
        Text(review.createTime)
          .fontSize(11)
          .fontColor('#999')
      }
      .width('100%')

      Row({ space: 8 }) {
        Rating({ rating: review.rating, indicator: true })
          .stars(5)
          .stepSize(0.5)
          .height(14)
        Text(review.rating.toFixed(1))
          .fontSize(12)
          .fontColor('#FFB300')
      }

      Text(review.content)
        .fontSize(14)
        .fontColor('#666')
        .width('100%')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }

  @Builder
  WriteReviewSheet() {
    Column({ space: 16 }) {
      Text('撰写评价')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#182431')

      Text('请为商品评分')
        .fontSize(14)
        .fontColor('#666')

      Rating({ rating: this.userRating, indicator: false })
        .stars(5)
        .stepSize(1)
        .height(40)
        .onChange((value: number) => {
          this.userRating = value
        })

      TextInput({ placeholder: '分享您的使用体验...', text: $$this.userComment })
        .width('100%')
        .height(120)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)

      Button('提交评价')
        .width('100%')
        .height(48)
        .backgroundColor('#FF6F00')
        .onClick(() => {
          this.submitReview()
        })
    }
    .width('100%')
    .padding(24)
    .backgroundColor(Color.White)
    .borderRadius({ topLeft: 20, topRight: 20 })
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column({ space: 12 }) {
        this.OverallRatingSection()
        this.DimensionRatingSection()

        Text(`用户评价 (${this.productRating.totalCount})`)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#182431')
          .width('100%')
          .padding({ left: 16, top: 8 })

        List({ space: 12 }) {
          ForEach(this.productRating.reviews, (review: ReviewItem) => {
            ListItem() {
              this.ReviewItemCard(review)
            }
          })
        }
        .width('100%')
        .layoutWeight(1)
        .padding({ left: 16, right: 16 })
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F1F3F5')

      // 底部写评价按钮
      Button('写评价')
        .width('90%')
        .height(48)
        .backgroundColor('#FF6F00')
        .margin({ bottom: 24 })
        .onClick(() => {
          this.isWritingReview = true
        })
    }
    .width('100%')
    .height('100%')
  }

  private async submitReview() {
    const reviewData = {
      rating: this.userRating,
      content: this.userComment,
      timestamp: new Date().toISOString()
    }
    console.info('提交评价:', JSON.stringify(reviewData))
    // 执行网络请求...
    this.isWritingReview = false
    this.userRating = 5.0
    this.userComment = ''
  }
}

5.3 实战要点总结

  1. 模式区分使用:综合评分和单项评分展示使用 indicator: true 只读模式,写评价弹窗中使用 indicator: false 交互模式,两种模式各司其职
  2. 精度差异化配置:展示场景使用 stepSize(0.1) 保证精度,输入场景使用 stepSize(1) 简化用户操作
  3. 组件复用:通过 @Builder 将评价卡片、评分区块封装为可复用的构建函数,提升代码可维护性
  4. 数据驱动:评价列表使用 ForEach 动态渲染,配合后端接口可轻松实现分页加载

六、高阶自定义:打造品牌专属评分系统

6.1 自定义星级图标

通过 starStyle 属性,可以将默认的星形图标替换为品牌专属图标(如爱心、笑脸、钻石等):

@State customRating: number = 3.5

Rating({ rating: this.customRating, indicator: false })
  .stars(5)
  .stepSize(0.5)
  .starStyle({
    backgroundUri: '/common/images/heart_empty.png',
    foregroundUri: '/common/images/heart_filled.png',
    secondaryUri: '/common/images/heart_half.png'
  })
  .onChange((value: number) => {
    this.customRating = value
  })

6.2 动态评分反馈

结合 animateTo 为评分变化添加动画效果,提升交互质感:

@State animatedRating: number = 0

Rating({ rating: this.animatedRating, indicator: false })
  .stars(5)
  .stepSize(0.5)
  .onChange((value: number) => {
    animateTo({ duration: 200, curve: Curve.EaseOut }, () => {
      this.animatedRating = value
    })
  })

6.3 评分标签联动

在用户评分过程中,根据评分值动态显示对应的文字标签,增强反馈感:

@State currentScore: number = 0

private getRatingLabel(score: number): string {
  if (score <= 1) return '非常不满意'
  if (score <= 2) return '不满意'
  if (score <= 3) return '一般'
  if (score <= 4) return '满意'
  return '非常满意'
}

Column({ space: 12 }) {
  Rating({ rating: this.currentScore, indicator: false })
    .stars(5)
    .stepSize(1)
    .onChange((value: number) => {
      this.currentScore = value
    })

  Text(this.getRatingLabel(this.currentScore))
    .fontSize(16)
    .fontColor(this.currentScore >= 4 ? '#4CAF50' : '#FF9800')
    .animation({ duration: 200 })
}

6.4 多维度评分表单

在服务评价场景中,通常需要用户对多个维度分别评分:

interface ServiceDimension {
  name: string
  score: number
}

@State dimensions: ServiceDimension[] = [
  { name: '服务态度', score: 5 },
  { name: '响应速度', score: 5 },
  { name: '解决效果', score: 5 }
]

Column({ space: 16 }) {
  ForEach(this.dimensions, (dim: ServiceDimension, index: number) => {
    Row({ space: 12 }) {
      Text(dim.name)
        .fontSize(14)
        .fontColor('#666')
        .width(80)

      Rating({ rating: dim.score, indicator: false })
        .stars(5)
        .stepSize(1)
        .onChange((value: number) => {
          this.dimensions[index] = { ...dim, score: value }
        })

      Text(`${dim.score}`)
        .fontSize(14)
        .fontColor('#FFB300')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
  })
}

七、性能优化与最佳实践

7.1 图片资源优化

自定义星级图片时,应注意以下优化策略:

  • 尺寸适配:根据目标显示尺寸提供适当分辨率的图片,避免过大图片造成内存浪费
  • 格式选择:优先使用 PNG 格式(支持透明通道),纯色图标可考虑使用矢量图或系统绘制替代
  • 缓存策略:频繁使用的评分图标应置于应用资源目录,利用系统资源缓存机制减少重复加载

7.2 列表场景性能

在评价列表中,每个列表项都可能包含 Rating 组件。为避免长列表滚动时的性能问题:

List() {
  ForEach(this.reviews, (review: ReviewItem) => {
    ListItem() {
      ReviewCard({ review: review })
    }
    .reuseId('review_item')  // 启用组件复用
  })
}

通过设置 reuseId 启用列表项组件复用,可显著降低 Rating 组件的重复创建开销。

7.3 可访问性支持

为评分组件添加语义化标签,确保视障用户能够正确理解评分信息:

Rating({ rating: this.score, indicator: true })
  .accessibilityText(`商品评分 ${this.score} 分,满分 5 分`)
  .accessibilityDescription('该商品的用户综合评分')

7.4 状态持久化

在写评价场景中,应防止用户意外退出导致已填写的评分和评论丢失:

@State draftRating: number = 0
@State draftComment: string = ''

aboutToDisappear() {
  // 页面销毁时保存草稿
  AppStorage.set('rating_draft', JSON.stringify({
    rating: this.draftRating,
    comment: this.draftComment
  }))
}

aboutToAppear() {
  // 页面加载时恢复草稿
  const draft = AppStorage.get<string>('rating_draft')
  if (draft) {
    const data = JSON.parse(draft)
    this.draftRating = data.rating
    this.draftComment = data.comment
  }
}

八、常见问题与解决方案

Q1:indicator=true 时星星变得很小,如何调整?

indicator 模式下默认高度为 12vp,如需更大尺寸,可显式设置宽高:

Rating({ rating: 4.5, indicator: true })
  .width(200)
  .height(28)  // 显式设置高度覆盖默认值

Q2:自定义图片设置后显示为灰色默认星?

starStyle 的三个字段必须同时设置有效值,否则组件会回退到默认灰色星形。请检查:

  • 图片路径是否正确(支持本地路径和网络 URL)
  • 图片资源是否已正确打包到应用中
  • 三个 URI 字段是否均有非空值

Q3:如何实现十分制评分?

stars 属性设置为 10 即可:

Rating({ rating: 8.5, indicator: true })
  .stars(10)
  .stepSize(0.5)

注意:当 stars 较大时,建议适当增加组件宽度,避免星级图片被过度压缩。


九、总结

本文从组件架构、核心 API、基础用法、进阶实战、高阶自定义以及性能优化六个维度,全面解析了 HarmonyOS Rating 评分组件的开发方案。通过电商商品评价系统的完整案例,展示了如何将 Rating 与多维评分、评论列表、数据持久化等实际业务深度结合。

在这里插入图片描述

核心要点回顾

  • Rating 采用 rating + indicator 双参数接口,一个组件覆盖输入与展示两种模式
  • stars / stepSize / starStyle 三大属性精准控制评分总数、精度和视觉风格
  • 三态渲染模型(foreground / secondary / background)支撑半星及更精细的评分展示
  • 生产级应用需关注 模式区分、精度配置、组件复用、状态持久化 四大核心问题
  • 配合 @BuilderanimateToAppStorage 等 ArkUI 特性可打造沉浸式评分体验

Rating 组件虽然接口简洁,但要在复杂业务场景中用得优雅、稳定、可扩展,仍需开发者对交互细节和性能特性有深入理解。希望本文能为你的鸿蒙应用评分系统开发提供有价值的参考。


转载自:https://blog.csdn.net/u014727709/article/details/163450134
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐