HarmonyOS 6商城开发学习:商品滑动评分五星动效——Progress ContentModifier与手势拖选评分值实战
在HarmonyOS 6购物比价或电商类应用中,商品评价页常用"五星评分"组件,产品希望不只是静态展示——用户手指在五星条上左右滑动时能实时点亮星星、带有微小填充动效(类似原生评分条渐变出现),且支持半星/整星两种模式。直接用五个Image拼手势虽可行但难做平滑过渡。
官方行业实践明确指出:可用 Progress组件 + ProgressConfigurationContentModifier 自定义内容区(绘制星星/条形)+ PanGesture驱动评分值 实现可交互动画评分条。本文将完整实现带滑动选值、整星/半星切换、微填充动效的五星评分组件。
一、需求拆解与设计
1. 交互规格
|
项目 |
说明 |
|---|---|
|
最大值 |
5 星 |
|
当前值 |
|
|
手势 |
横向拖拽自动算分,点击也可直接设点 |
|
视觉 |
背景灰星 + 前景金色彩条/星(按比例裁剪),带 |
|
模式 |
可切 |
2. 实现选型
-
Progress+ProgressConfigurationContentModifier:用 Progress 的value/max控制填充比例(0~5),ContentModifier 内用 Canvas 或 Column(Row星图)+clip(Shape)模拟前景裁切——最简是用Stack[背景星Row / 前景星Row.clip(Rect)]包在 Progress 的 contentModifier 中 -
PanGesture+onClick:算触摸点 X 占宽比 → 映射分值 →animateTo(()=>this.score=newVal)
二、五星评分组件完整实现
// components/StarRating.ets
import { common } from '@kit.AbilityKit';
@Component
export struct StarRating {
// ===== 对外属性 =====
maxStar: number = 5;
@Link score: number; // 当前评分 0~maxStar
step: number = 1; // 1=整星, 0.5=半星
starSize: number = 28; // 单颗星 vp
activeColor: string = '#FF9800';
inactiveColor: string = '#E0E0E0';
showScoreText: boolean = true;
// 组件宽(用于手势映射)
private compWidth: number = 0;
// 将 score 量化到 step
private quantize(v: number): number {
const s = Math.max(0, Math.min(this.maxStar, v));
if (this.step >= 1) return Math.round(s);
return Math.round(s / 0.5) * 0.5;
}
build() {
Row({ space: 6 }) {
// === Progress 做评分条容器(value驱动前景裁剪)===
Progress({
value: this.score,
total: this.maxStar,
type: ProgressType.Ring // Ring仅占位,真正显示靠contentModifier
})
.width(this.maxStar * this.starSize)
.height(this.starSize)
.progressStyle({ strokeWidth: 0 }) // 隐藏原生环
.contentModifier({
// ✅ ContentModifier:自定义 Progress 内容区画星星
factory: (config: ProgressConfiguration) => {
this.starContent(config);
}
})
// 手势:横向拖拽算分
.gesture(
PanGesture({ direction: PanDirection.Horizontal })
.onActionUpdate((e: GestureEvent) => {
const localX = e.fingerList[0].x;
let ratio = localX / (this.maxStar * this.starSize);
ratio = Math.max(0, Math.min(1, ratio));
const raw = ratio * this.maxStar;
animateTo({ duration: 80 }, () => {
this.score = this.quantize(raw);
});
})
.onActionEnd(() => {
// 动画弹回取整(视觉确认)
animateTo({ duration: 150, curve: Curve.EaseOut }, () => {
this.score = this.quantize(this.score);
});
})
)
// 点击直接设分
.onClick((e: ClickEvent) => {
const localX = e.x;
let ratio = localX / (this.maxStar * this.starSize);
ratio = Math.max(0, Math.min(1, ratio));
this.score = this.quantize(ratio * this.maxStar);
})
.onAreaChange((_, nv) => {
this.compWidth = nv.width as number;
})
// 分数文本
if (this.showScoreText) {
Text(`${this.score.toFixed(this.step < 1 ? 1 : 0)} / ${this.maxStar}`)
.fontSize(13)
.fontColor('#888')
.margin({ left: 4 })
}
}
}
// ===== 自定义星星绘制(背景+前景裁剪)=====
@Builder
starContent(_config: ProgressConfiguration) {
// 背景:灰星行
Row() {
ForEach(Array(this.maxStar).fill(0), (_i, idx) => {
Image($r('sys.media.ohos_ic_public_star_filled'))
.width(this.starSize)
.height(this.starSize)
.fillColor(this.inactiveColor)
})
}
.width('100%')
.height('100%')
// 前景:金色彩条裁剪到 Progress.value/maxStar 比例
Stack() {
Row() {
ForEach(Array(this.maxStar).fill(0), (_i, idx) => {
Image($r('sys.media.ohos_ic_public_star_filled'))
.width(this.starSize)
.height(this.starSize)
.fillColor(this.activeColor)
})
}
.width('100%')
.height('100%')
}
.clipShape(Rect()
.width((this.score / this.maxStar) * (this.maxStar * this.starSize))
.height(this.starSize)
.alignLeft() // 从左往右裁切
)
.alignSelf(HorizontalAlign.Start)
}
}
关键实现解读
|
做法 |
作用 |
|---|---|
|
|
完全替换 Progress 默认绘制,在里面叠背景星Row + 前景星Row(前景Row用 |
|
|
前景只显示左侧已评分部分 → 产生"点亮"效果 |
|
|
拖拽时实时算 |
|
|
点按直接量化设分(便于快速选星) |
|
|
按 |
若想用 Canvas 自绘五角星(而非系统
ohos_ic_public_star_filled图标),在starContent中用Canvas(ctx).onReady(drawStarPath)替代 Image Row 即可,裁剪原理相同。
三、商品评价页调用示例
// pages/GoodsReviewPage.ets
import { StarRating } from '../components/StarRating';
@Entry
@Component
struct GoodsReviewPage {
@State rating: number = 0;
build() {
Column({ space: 20 }) {
Text('商品评分')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 32, left: 16 })
StarRating({
score: this.rating, // 双向绑定
step: 0.5, // 半星模式
starSize: 32,
showScoreText: true
})
// 整星模式示例(可切 tab 展示)
StarRating({
score: this.rating,
step: 1,
starSize: 28,
showScoreText: true
})
Text('当前评分值:' + this.rating)
.fontSize(14)
.fontColor('#666')
.margin({ top: 8 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F6F8')
}
}
四、避坑指南
|
问题 |
原因 |
修复 |
|---|---|---|
|
前景星不随 score 变 |
|
确保用 |
|
拖拽跳值不准 |
未用组件 |
|
|
半星量化偏差 |
JS 浮点 |
|
|
想支持只读展示 |
不加 PanGesture/onClick,只传 |
同组件只读模式——ContentModifier 仍正常工作 |
|
星星间距过大 |
ForEach Row 默认无 spacing |
Row 设 |
五、总结:滑动评分五星动效 SOP
-
用
Progress+ProgressConfigurationContentModifier 自定义内容区 -
背景 Row(灰星) / 前景 Row(金星) 叠放,前景
clipShape(Rect)宽度 =score/max × 总星宽 -
PanGesture.onActionUpdate 映射触摸X→分值→animateTo驱动 score;onClick点选 -
量化函数
quantize(raw, step) 按整星/半星四舍五入,确保最终值合规 -
只读展示去掉手势即可复用同组件
核心法则:HarmonyOS 6 中滑动评分 = "Progress ContentModifier 双 Row 星图 + Rect 裁剪比例 + PanGesture 映射 X→分值 animateTo",不依赖多个 Image 独立状态。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。
更多推荐



所有评论(0)