news 2026/10/21 15:26:03

HarmonyOS 6自研星级评分组件:Canvas绘制与手势交互实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 6自研星级评分组件:Canvas绘制与手势交互实战

1. 为什么自己动手写星级评分组件

先交代背景。我做鸿蒙应用开发有一段时间了,日常主要处理电商、内容社区这类强交互业务的页面。评价模块属于每个产品几乎都有的标配功能,用户打分、展示均分、列表里的评分摘要,到处都要用。项目做的时间越长越发现,系统自带的那个Rating组件,应付简单场景还行,真要放入真实产品里,总有一种“差一口气”的感觉。这个项目标题里的HarmonyOS 6星级评分实战,其实就是我在某个跨平台业务系统里沉淀下来的一个自研评价组件,今天把这套实现思路、完整代码和踩坑记录整理出来。

1.1 这个组件到底解决了什么问题

先看基础需求。一个合格的评分组件至少要满足三件事:能点击打分、能回显均分、能适配不同主题风格。听起来很简单,但放进真实业务里问题就来了:产品希望评分星星有渐变光效;希望点击时有弹性反馈;希望平均分展示时支持半颗星;希望打分后有个炫一点的动效再提交。用系统的Rating组件,前三条基本做不到,它只支持固定样式的星型资源,动画和主题定制能力很弱。

所以我在HarmonyOS 6上实现了一个完全自定义的星级评分组件,核心点包括:

  • 纯ArkTS + ArkUI声明式语法实现,不依赖原生插件
  • 支持整星、半星、任意精度三种评分模式
  • 星星形状完全由Canvas绘制,颜色、尺寸、阴影全部可配
  • 点击、滑动打分都有触控反馈,评分过程直观流畅
  • 封装成通用组件,传入配置即可复用

这套方案的适用范围很广:电商订单评价、应用商店评分、内容质量打分、客服满意度调查,凡是能抽象成“五颗星打分”的场景,拿过去改改配置就能用。

1.2 HarmonyOS 6环境下组件开发的变化

HarmonyOS 6对我来说最明显的变化是ArkUI的渲染效率和状态管理能力都有提升。我实测下来,用Canvas画5颗星、每帧更新局部高亮状态,性能开销非常小,没有出现掉帧。这得益于新版对于组件刷新范围的缩小,@State绑定的值变化时,只刷新生效的那部分UI,而不是整棵树重建。

另一个值得提的点是Panel和bindSheet这类半屏交互组件变得更稳定了,我评价组件配合半屏面板弹出展示时,过渡动画流畅了很多。如果你之前是在旧版本上开发,升级到HarmonyOS 6后最直观的感受是编译速度、热重载效率和组件渲染稳定性的提升,特别是在低端测试机上差距尤其明显。

2. 方案选型:为什么不直接拿系统Rating改一改

系统自带的Rating组件不是不能用,它适合的是原型验证和极简场景,比如内部工具、后台管理页面这种对视觉效果要求不高的情况。但在面向用户的正式产品里,局限性很快暴露。

2.1 系统Rating组件的优势与死角

先看它好用的地方:API极其简单,几行代码就能出一个可打分星星条;性能有保障,毕竟是系统控件,绘制效率很高;基础参数比如总星数、步长、当前值都能直接设置。

但死角也很明显:

  • 只能使用本地图片资源作为星型素材,想要动态改颜色、改形状很麻烦
  • 样式定制能力弱,阴影、光效、渐变全靠图片做,不同尺寸要准备多套图
  • 不支持半星以上的任意精度,stepSize只能设固定步长,做到0.5就到头了
  • 打分后的交互动效基本为零,点击只是改变显示,没有弹性、没有过渡

我的结论是:如果目标是快速出一个能用的评分条,直接上Rating;如果你要的是一个能代表产品气质的评分交互,那就只能自研。

2.2 三个备选路线对比

我做技术方案之前习惯先列几条路线,对着优缺点比一轮再做决定,这样后面开发时心里踏实。

方案实现方式优点缺点适用场景
系统Rating组件原生控件配置资源开发快、稳定定制弱、无动画原型、内部工具
预置图片+Stack叠层灰星星和亮星星两张图叠加效果可控、实现简单需要多套图片、半星处理麻烦样式固定的小型项目
Canvas自绘+手势计算代码画星星,触摸实时算评分完全自定义、动效自由、一套代码开发量大、需处理坐标计算正式产品、多主题需求

我最终选的是第三种。原因很简单:这个组件要出现在多个页面里,不同页面主题色不一样,评分场景有只读展示、有点击打分、有半星摘要,一套代码全搞定最划算。而且View绘制性能在HarmonyOS 6上表现足够好,自绘完全不是性能瓶颈。

3. 自研评分组件的核心设计与实现

这一节是整篇博文的重点,我会把组件从设计到实现的关键环节拆开讲,重点讲清楚每一个“为什么”:为什么用Canvas、为什么这样算坐标、为什么状态放这里、手势边界怎么处理。

3.1 整体架构与数据流转设计

组件拆成三层:

  1. 展示层:五颗星的绘制与高亮差异展示
  2. 交互层:触摸事件的捕捉与评分计算
  3. 数据层:组件内外部的状态同步与回调

对应到ArkTS实现上,展示层就是Canvas的onReady绘制逻辑,交互层是挂在Canvas上的onTouch事件,数据层用@Prop接收初始值、用@Watch监听变化、通过@Link或者回调函数通知外部更新。

这里有个设计经验:组件不应该自己维护评分数据,评分一定来自业务层。所以组件只接收初始分数,用户交互产生的分数变化通过回调抛给调用方,由调用方决定是提交到服务端,还是只改本地状态。这样的好处是组件可以无脑复用,不用担心数据污染。

3.2 五角星的数学坐标计算

既然要自绘星星,第一步就得把五角星的坐标算出来。五角星本质上是10个顶点组成的多边形,5个外顶点在同一个外圆上,5个内顶点在同心内圆上,内外顶点交替排列。

设外圆半径为R,内圆半径为r,五角星的标准比例是:内圆半径约等于外圆半径的0.382倍。这个比例关系是从几何光学里来的,使用这个比例画出来的五角星视觉上最协调饱满。

顶点的角度计算逻辑如下:从正上方也就是-90度位置开始,每72度一个外顶点;内顶点位于两个外顶点之间,所以在每个外顶点角度基础上加36度。用极坐标公式就能算出一串点坐标:

x = cx + radius * cos(angle * PI / 180) y = cy + radius * sin(angle * PI / 180)

把这10个点按顺序连起来闭合,就是一个标准的五角星。之所以强调这个换算过程,是因为很多新手直接把外顶点和内顶点用同样的角度数量画,画出来的星星会扭曲,看起来像个瘪掉的多边形。

3.3 Canvas画星星的完整代码

组件里的绘制核心是下面这段drawStar方法,参数包括中心点、外半径、内半径和填充颜色,负责绘制一颗星并填充成实心形状。

private drawStar(ctx: CanvasRenderingContext2D, cx: number, cy: number, outerR: number, color: Color) { const innerRatio = 0.382; const innerR = outerR * innerRatio; const points: Array<number> = []; for (let i = 0; i < 5; i++) { const outerAngle = ((-90 + i * 72) * Math.PI) / 180; const innerAngle = ((-90 + i * 72 + 36) * Math.PI) / 180; points.push(cx + outerR * Math.cos(outerAngle)); points.push(cy + outerR * Math.sin(outerAngle)); points.push(cx + innerR * Math.cos(innerAngle)); points.push(cy + innerR * Math.sin(innerAngle)); } ctx.beginPath(); ctx.moveTo(points[0], points[1]); for (let i = 2; i < points.length; i += 2) { ctx.lineTo(points[i], points[i + 1]); } ctx.closePath(); ctx.fillStyle = color; ctx.fill(); }

这段代码是最核心的基础函数,后面所有复杂效果,比如渐变、光晕、半星遮罩,都建立在这个基础绘制之上。在组件的onReady回调里,我会循环调用这个方法5次,每次根据当前的评分值决定这颗星用什么颜色填充。

3.4 高亮逻辑:按评分值决定每颗星的状态

假设评分值是当前分数(支持小数,比如3.5或者3.6),那么第i颗星的填充比例判断如下:

  • 如果 分数 >= i + 1:整颗亮
  • 如果 分数 > i 且 分数 < i+1:需要画部分填充或者半星效果
  • 如果 分数 <= i:整颗暗

这里有个视觉细节:一颗星从“暗”到“亮”不是瞬间完成的,我加了平滑过渡。实现方式是保存一个动画进度值,用animateTo或者逐帧插值。不过初版可以先不做动画,先把逻辑跑通,后面再说动效增强。

评分展示模式下,外壳我画成浅灰色,高亮部分画成品牌主色。打分模式下,未选中的星画成深灰色,选中的画成金色,色值都是外部传入的配置项。

4. 手势交互与评分计算的完整实现

画好星星只是第一步,真正让组件“活”起来的是触摸交互。这部分的坑最多,包括坐标转换、边界判断、滑动连续性,以及和页面滚动手势的冲突处理。

4.1 触摸事件捕获与坐标换算

我在Canvas组件上挂onTouch事件,事件对象里有手指的本地坐标,也就是以Canvas左上角为原点的坐标。需要说明的是这个本地坐标是vp单位,如果你在代码里用的也是vp尺寸,那直接拿来算就行,不需要再转换。

每一颗星的区域计算方式:假设星星总宽度是starWidth,星星之间的水平间距是spacing,那么第i颗星的x范围是从 左内边距 + i*(starWidth+spacing) 到 左内边距 + i*(starWidth+spacing) + starWidth。手指坐标落在哪个区间,就算打到第几颗星。

有个细节要特别提醒:组件最外层如果有水平padding,必须同步纳入偏移计算,否则手指点到星星左侧会错位。

4.2 整星、半星与任意精度的切换

评分精度的需求在不同场景里差异很大:打分页通常是整颗星,均分摘要页需要半星,有些业务还希望0.1精度。这个组件用一个mode枚举来支持三种模式。

整星模式的判定逻辑最直观,记录手指按下时的x坐标,映射到第几颗星序号,四舍五入到整数。

半星模式则在整星基础上加一步判断:手指落在某颗星区域内的左半边算半颗,右半边算一颗。这里的左半边右半边是以星星中心线为界的。

任意精度模式就是按比例计算小数,比如星星宽度40vp,手指在星星区域内偏右了20vp,那这一颗的贡献就是0.5分。

function calcRatingByTouch(touchX: number, starWidth: number, spacing: number, mode: RatingMode) { const totalUnit = starWidth + spacing; let index = (touchX - padding) / totalUnit; index = Math.max(0, Math.min(4, index)); const inStarIndex = Math.floor(index); const offsetInStar = index - inStarIndex; if (mode === RatingMode.Half) { if (offsetInStar < 0.5) { return inStarIndex + 0.5; } else { return inStarIndex + 1; } } else if (mode === RatingMode.Integer) { return offsetInStar < 0.5 ? inStarIndex : inStarIndex + 1; } else { return inStarIndex + Math.min(1, offsetInStar * 2); } }

这个方法的返回值就是当前评分,随后赋值给状态变量并重新绘制。

4.3 滑动手势连续评分的细节处理

打分组件的体验核心在于“滑动连续评分”:手指按下去之后不抬起来,左右滑动可以不断改变分数,松手时才算最终值。这个交互在实现上有一个关键点:onTouch事件里区分Down、Move、Up三个状态。

按下的时候记录起始分数,作为滑动比较的基线;移动的时候实时计算新分数并刷新UI;抬起时把最终值回调给调用方。

还有一个我在真机调试时遇到的细节:手指快速滑动时,onTouch事件可能不会逐帧触发,导致分数跳变。解决方案是在Move状态里不依赖事件个数,而是每次都用当前的x坐标重新计算分数。这样即使事件间隔较大,最终值也不会出错。

4.4 与外层Scroll滑动手势冲突的处理

这是最容易被忽略的问题。如果评价组件放在一个可滚动页面里,手指在星星上横向滑动时可能触发外层页面的纵向滚动。解决思路有两个:一是组件自己消费掉横向滑动事件,设置手势优先级;二是判断手指位移方向,如果是纵向就不拦截,横向就拦截。

我用的方案是给组件的onTouch在Down时记录起始坐标,Move时计算位移的横纵比,如果横向位移大于纵向就拦截事件不再向外传递。配合GestureGroup里的ParallelGesture,在 HarmonyOS 6 上处理得比较干净。

5. 进阶优化:从“能用”到“好用”的打磨细节

基础功能跑通之后,我开始针对真实使用体验做细节优化。很多开发到这里就觉得完事了,但他们忽略的恰恰是一个评分组件最容易被用户感知差异的地方。

5.1 半星摘要模式与只读模式的处理

展示平均分和点击打分是两种完全不同的使用场景。展示模式的核心是“不能误触”和“信息清晰”,我通过参数readOnly控制,readOnly为true时完全忽略触摸事件,组件表现为纯展示。

半星摘要模式比较特殊。均分可能是3.3分或者4.7分,直接画满或不满都不准确。我给这种模式设计了两种表达方式:一种是精确到小数的高亮比例,另一种是星星内做扇形或者矩形裁剪,我选了后者,因为视觉上更直观。具体做法是先用暗色画完整颗星,再在左上角开始按比例用亮色绘制一个覆盖宽度等于分数百分比的长方形区域。不需要裁剪五角星边缘,用Canvas的clip方法就能完美实现。

5.2 点击与松开的动效反馈

用户的点火感往往来自动效。我做了一个“按压放大”效果:当检测到手指在某颗星上时,这颗星的外半径临时放大1.15倍,并给Canvas设置一个较亮的描边,松手后恢复。这个放大效果用animateTo包裹,时长180毫秒,曲线用EaseOut,手感很跟手。

还有一个细节是评分结果变化时,整个组件有一个轻微的回弹效果,就像弹簧按下去弹回来那样。实现上就是给评分值变化时触发一个水平位移的插值动画。

5.3 主题适配与多尺寸适配

组件所有颜色、尺寸都是通过构造参数传入。我把可配置项集中到了一个RatingConfig对象里,包含星数、尺寸、间距、主题色、暗星颜色、半星模式、是否只读、动画时长等。外部传入一个配置对象就能完成整套视觉风格切换。

针对不同屏幕尺寸,我给星星尺寸提供了strat或string两种配置方式,传number表示固定vp值,传string如'large'/'small'则走组件内部的归一化规则,具体换算基于屏幕宽度计算。实测在手机和平板上效果都正常,不出现星星过大或挤压的情况。

6. 完整Demo演示与代码解读

前面讲的是逻辑拆解,这一节我把一个可运行的完整页面Demo贴出来。你拿到里可以直接创建一个ArkTS页面编译运行。

6.1 页面结构与状态定义

页面里我要同时演示三种模式:只读展示、半星打分、整星打分。所以定义了三个状态变量,分别管理当前三个区域的分数值。

@Entry @Component struct RatingDemoPage { @State readOnlyScore: number = 3.7; @State halfScore: number = 2.5; @State integerScore: number = 4; private ratingConfigReadOnly: RatingConfig = { starCount: 5, mode: RatingMode.Half, readOnly: true, activeColor: '#FFB800', inactiveColor: '#D8D8D8', starWidth: 32, spacing: 8 }; build() { Column({ space: 24 }) { Text('只读均分展示') StarRating({ config: this.ratingConfigReadOnly, value: this.readOnlyScore, onChange: (v: number) => { this.readOnlyScore = v; } }) Text('半星打分') StarRating({ config: { starCount: 5, mode: RatingMode.Half, readOnly: false, activeColor: '#FFB800', inactiveColor: '#D8D8D8', starWidth: 40, spacing: 10 }, value: this.halfScore, onChange: (v: number) => { this.halfScore = v; } }) Text('整星打分') StarRating({ config: { starCount: 5, mode: RatingMode.Integer, readOnly: false, activeColor: '#FF9500', inactiveColor: '#CCCCCC', starWidth: 48, spacing: 12 }, value: this.integerScore, onChange: (v: number) => { this.integerScore = v; } }) }.width('100%').padding(20) } }

组件的调用方式很清爽,每个实例只需传配置、初始值和回调,内部细节完全隔离。

6.2 组件内部布局结构

StarRating组件的内部结构很简单:一层Stack容器,里面放一个铺满的Canvas。布局不复杂是因为星星位置全部由Canvas绘制控制,不需要叠加多个子组件。

@Component struct StarRating { @Prop config: RatingConfig; @Prop value: number; @State currentValue: number = 0; private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(); aboutToAppear(): void { this.currentValue = this.value; } build() { Column() { Canvas(this.context) .width('100%') .aspectRatio(5) .onReady(() => { this.drawStars(); }) .onTouch((event: TouchEvent) => { this.handleTouch(event); }) } } }

组件的宽高比用aspectRatio(5)控制,因为你画5颗星横着排,高矮比例基本固定。这个写法的好处是不用写死宽度,外部给多少宽,星星自动拉伸,适配性强。

6.3 核心绘制流程逐步解析

现在看组件的核心方法drawStars的完整流程。这个方法负责根据当前分数绘制全部星星,并在不同的评分精度下采用不同策略。

private drawStars() { const ctx = this.context; const canvasWidth = this.context.width; const canvasHeight = this.context.height; const cfg = this.config; const totalSpacing = cfg.spacing * (cfg.starCount - 1); const starWidth = (canvasWidth - totalSpacing) / cfg.starCount; const starHeight = starWidth; const topOffset = (canvasHeight - starHeight) / 2; ctx.clearRect(0, 0, canvasWidth, canvasHeight); for (let i = 0; i < cfg.starCount; i++) { const cx = i * (starWidth + cfg.spacing) + starWidth / 2; const cy = topOffset + starHeight / 2; const outerR = starWidth / 2; if (cfg.mode === RatingMode.Integer || cfg.mode === RatingMode.Half) { ctx.save(); ctx.beginPath(); ctx.rect(i * (starWidth + cfg.spacing), topOffset, starWidth, starHeight); ctx.clip(); const isActive = this.currentValue >= i + 1; this.drawStar(ctx, cx, cy, outerR, isActive ? cfg.activeColor : cfg.inactiveColor); ctx.restore(); } if (cfg.mode === RatingMode.Half) { const remaining = this.currentValue - i; if (remaining > 0 && remaining < 1) { ctx.save(); ctx.beginPath(); ctx.rect(i * (starWidth + cfg.spacing), topOffset, starWidth * 0.5, starHeight); ctx.clip(); this.drawStar(ctx, cx, cy, outerR, cfg.activeColor); ctx.restore(); } } } }

这段代码的巧妙之处在于半星时用了两次clip:第一层clip控制每一颗星的矩形区域,防止画到旁边去;第二层clip把高亮裁剪到左半边。两次裁剪叠加后,就能精确实现左半亮右半暗的效果。

6.4 触摸处理的回调与内部状态刷新

触摸处理部分最关键的是要把组件内部currentValue更新和外部回调区分开。组件内部立刻刷新视觉是让用户感觉“跟手”的关键,外部回调则在最终值时触发一次,避免频繁通知父组件导致性能浪费。

private handleTouch(event: TouchEvent) { if (this.config.readOnly) { return; } const touchX = event.touches[0].x; const canvasWidth = this.context.width; const cfg = this.config; const totalSpacing = cfg.spacing * (cfg.starCount - 1); const starWidth = (canvasWidth - totalSpacing) / cfg.starCount; const rawValue = calcRatingByTouch(touchX, starWidth, cfg.spacing, cfg.mode); if (event.type === TouchType.Down || event.type === TouchType.Move) { // 实时刷新UI,但不触发外部回调 this.currentValue = Math.min(cfg.starCount, Math.max(1, rawValue)); this.drawStars(); } else if (event.type === TouchType.Up) { // 松手时才通知外部评分结果 this.currentValue = Math.min(cfg.starCount, Math.max(1, rawValue)); this.drawStars(); this.onChange(this.currentValue); } }

这里值得注意的点是calcRatingByTouch计算时用到的starWidth必须和绘制时用到的starWidth保持一致,不然评分会和视觉错位。

6.5 配置对象的TS定义

最后补上RatingConfig和RatingMode的定义,方便你直接拷贝使用。

export enum RatingMode { Integer = 0, Half = 1, Arbitrary = 2 } export class RatingConfig { starCount: number = 5; mode: RatingMode = RatingMode.Integer; readOnly: boolean = false; activeColor: string = '#FFB800'; inactiveColor: string = '#D8D8D8'; starWidth: number | string = 40; spacing: number = 8; }

7. 常见问题与排查技巧实录

开发过程中我前后踩了不少坑,挑几个最有代表性的记录在这里,按出现频率从高到低排序。

7.1 Canvas onReady不执行的问题

组件挂在页面里,onReady却迟迟不触发,页面看起来一片空白。排查后发现是两个原因造成的:一是Canvas没有显式设置宽高,二是在aboutToAppear里过早访问了context。

正确做法是给Canvas设置明确的width和height,或者设置width('100%')加aspectRatio。另外不要在aboutToAppear里调用绘制逻辑,等onReady回调触发后再画。

7.2 触摸坐标与绘制坐标不一致

评分算出来总差那么一点点,点第4颗星的边缘却触发第3颗星。出现这个问题十有八九是计算触摸位置时,分母用了整块屏幕宽度,而实际星星只占了其中一部分。

我的解决方式:绘制starWidth和计算触摸的starWidth用同一个方法计算,不要在两处各自写一遍公式。把这个计算逻辑抽成一个私有方法,返回宽度和间距,绘制、触摸计算都调用它。

7.3 评分滑动过快时数值跳跃

快速左右滑动时,评分值并不是连续变化的,而是像打点一样跳着走。原因在于TouchEvent的Move事件并不是每帧都触发,快速移动时事件间隔较大。

解决办法是在Move事件里不依赖增量值,直接根据当前触摸点x坐标重算评分。每次触发Move都全局计算一遍,数值就是连续的。这个思路在很多需要跟手操作的组件里都适用。

7.4 组件在低端机上第一次渲染掉帧

有用户反馈说组件第一次出现在页面里时,星星像是“慢慢浮现”的,体验不好。原因是我在Canvas初次绘制时同步做了很多后续才需要的计算,比如图片加载和阴影生成。

优化方案是首帧只画最基础的星星轮廓,不加载阴影和渐变等复杂效果;等onReady之后的下一帧再补全阴影,利用requestAnimationFrame调度。首帧压力小,后续再补细节,渲染节奏就顺了。

8. 组件性能优化与后续扩展思路

这个组件目前的性能表现对我来说已经够用,但如果你要在一个列表页里渲染几十条评分摘要,就需要再加一些优化手段。

8.1 减少Canvas频繁重绘的策略

在列表滚动场景,每次滑动都会触发onTouch的话会消耗大量算力。我给组件加了一个帧率控制策略:只有评分值变化时才重绘,评分不变时直接用缓存帧。实现上是用一个lastValue变量记录上一次绘制时的值,比较后再决定是否调用drawStars。

对于纯展示的评分摘要,可以进一步优化为:每颗星只一次性绘制,不做动态重绘,因为摘要场景的均分基本不会频繁变化。

8.2 使用LazyForEach配合列表优化的实践

如果你打算在商品列表页里放大量评分组件,建议配合LazyForEach懒加载。列表项里的评分组件只有在进入可视区域时才创建和初始化,离开可视区域就销毁,这样能显著降低首屏压力和内存占用。在HarmonyOS 6上,LazyForEach配合自定义组件的懒加载机制已经比较成熟,我实测一个包含100个评分项的列表也能保持流畅滚动。

8.3 无障碍与语义化

评分组件对无障碍的支持容易忽略,但确实重要。我在组件外部包了一层可聚焦的文本标签,动态朗读当前评分值。具体方法是在onReady后用accessibilityText设置语义文本,比如“当前评分4.5分,共5星”,这样读屏软件可以正确播报。

8.4 如何扩展成多位评分

电商评价页经常要同时给“商品质量”“物流速度”“服务态度”几个维度分别打分。要实现这个效果,不用改组件任何核心逻辑,外面包一层循环即可,每个维度创建一个StarRating实例。我实际项目中就是这么做的,配置数组化,每个维度带标题、分数、配置三项。

9. 最后分享几个我在实际项目里总结的经验

项目做完之后,我复盘了一下整个过程,有三个点值得拿出来重点说说。

第一,组件开发的调试成本比业务页面高很多,建议从一开始就做一个独立Demo页面,把各种边界情况放在一起测。评分组件涉及整星、半星、只读、滑动、多实例等状态组合,没有集中调试页面,后面每改一次都要人工多次验证,效率很低。

第二,评分结果的上报一定要做防抖和限频。用户打分时滑动过程中可能触发几十次onChange,你不能每次都往服务端发请求。正确的做法是组件内部只在松手时才回调最终值,如果一定要实时上报,也要加时间戳或距离阈值做节流。

第三,评分组件的视觉细节和业务强相关。不同产品对“打高分”的引导程度不一样,有的希望用户更容易打出五星,有的要求用户严格评价。针对这种需求,我给组件加了一个可配置的“吸附范围”,当用户拖到4.6星以上时,直接吸附到5星,超过3.8吸附到4星。这个开关在配置里默认关闭,需要时就打开。

星级评分看着是个小组件,但把触摸计算、Canvas绘制、状态管理、动效反馈串起来,里面的细节一点都不少。希望这篇文章能帮你做出一个真正好用、没有套路坑的评分组件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 15:23:26

银河麒麟V10部署GreatSQL:二进制包安装全流程与排障指南

KylinV10装GreatSQL&#xff0c;这几年国产化项目里越来越常见。我最近刚好在一套银河麒麟V10服务器上部署了一次&#xff0c;业务要求把现有数据库切换到GreatSQL。因为原系统是MySQL生态&#xff0c;迁移成本低&#xff0c;而且GreatSQL在MySQL 8.0的基础上做了不少优化&…

作者头像 李华
网站建设 2026/10/21 15:22:06

Claude Code模板库:面向工程可信度的AI编码提效范式

1. 这不是又一个“AI编程插件”&#xff0c;而是开发者私藏的效率杠杆 最近在几个技术群和内部分享会上&#xff0c;反复听到一句话&#xff1a;“别折腾配置了&#xff0c;直接装那个带模板库的Claude Code插件。”起初我以为是某个新出的VS Code扩展&#xff0c;直到某天帮一…

作者头像 李华
网站建设 2026/10/21 15:22:05

SQL Server下载与安装详解:版本选型、配置要点与常见问题排查

说起来&#xff0c;SQL Server应该是接触Windows环境后最常碰到的企业级数据库之一。我记得第一次给测试环境装SQL Server的时候&#xff0c;光下载就折腾了很久——入口找不对、版本选错、装到一半报错&#xff0c;后来踩完一圈坑才发现&#xff0c;下载与安装这套流程其实可以…

作者头像 李华
网站建设 2026/10/21 15:21:56

知识工作插件化:认知操作自动化的实践路径

1. 项目概述&#xff1a;这不是插件&#xff0c;而是一套知识工作者的“思维外挂”系统“knowledge-work-plugins”这个标题乍看像某个开源仓库的命名&#xff0c;但拆开来看——knowledge&#xff08;知识&#xff09;、work&#xff08;工作&#xff09;、plugins&#xff08…

作者头像 李华
网站建设 2026/10/21 15:21:48

解构lingbot-map:移动机器人轻量级地图管理框架设计与实战

做机器人这行的&#xff0c;绕不开一张图。这里说的图&#xff0c;不是给人看的导航地图&#xff0c;而是让机器人自己能懂的那张“底图”。我在给一个室内巡检机器人项目做导航方案时&#xff0c;反复试了好几种方案&#xff0c;最后沉淀下来一套专门的地图模块&#xff0c;代…

作者头像 李华
网站建设 2026/10/21 15:21:27

MySQL底层逻辑:从索引、事务到性能优化实战

1. 为什么我劝你先搞懂MySQL的底层逻辑 如果你是一个刚入行的后端开发&#xff0c;或者正在准备系统性学习数据存储的从业者&#xff0c;我估计你已经被MySQL相关的问题淹没过了。面经里翻来覆去就是索引失效、事务隔离级别、间隙锁、MVCC这些词&#xff0c;看起来每个字都认识…

作者头像 李华