PercentRatingBar
按滑动百分比来评分的控件
Install / Use
/learn @wolongalick/PercentRatingBarREADME
PercentRatingBar
按滑动百分比来评分的控件
依赖方式
buildscript {
repositories {
google()
jcenter()
}
}
dependencies {
implementation 'com.wolongalick.widget:PercentRatingBar:1.0.1'
}
快速使用
<com.wolongalick.widget.PercentRatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
完整使用
<com.wolongalick.widget.PercentRatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:ratingSelectedImg="@drawable/selected_star"
app:ratingNotSelectImg="@drawable/not_select_star"
app:ratingSelectedScore="3.7"
app:ratingTotalScore="10"
app:ratingPadding="2dp"
app:ratingIsSupportDrag="true"
app:ratingStep="exactly" />
自定义属性详解
属性名 | 含义| 对应java/kotlin方法 ---|---|--- ratingSelectedImg | 选中的星星图片资源id | setImageRes(Int, Int) ratingNotSelectImg| 未选中的星星图片资源id | setImageRes(Int, Int) ratingSelectedScore| 选中的星星个数评分(支持小数) | setScore(Float)和getScore() ratingTotalScore| 总分数 | setTotalScore(Int)和getTotalScore() ratingPadding| 星星之间的间距,单位px | setRatingPadding(Int) ratingIsSupportDrag| 是否支持拖动 | setRatingIsSupportDrag(Boolean)和getRatingIsSupportDrag() ratingStep| 星星步长(full:整颗星、half:半颗星、exactly:精确到具体刻度比例) | setStep(@RatingStep step: Int)
前言
公司的产品需要一个评分控件,并且分数并不仅仅是1.5、2.5这样的,而是要支持1.1、1.9分,并且星星的评分样式也要与分值完全对应 也就是要实现这种效果
我一听就懵逼了,这不是为难我么
不过既然产品既然提了需求,咱也得尽量去实现,否则以后还怎么愉快玩耍
需求描述
- 支持整颗星、半颗星和按百分比评分
- 支持滑动和点击评分
- 支持自定义星星图标和星星间距
- 支持...好了闭嘴吧...咱都给你实现了
需求分析
- 首先绘制星星很简单,调用canvas.drawBitmap就可以,多个星星for循环绘制即可
- 复杂的地方有两处:a.如何绘制残缺星星,b:如果在滑动时,将滑动位置转化为分数
开始写demo
我们先画一颗星看看效果
不过没关系,我们可以换个改为在onMeasure中获取bitmap,并将其作为全局变量存起来(因为要计算星星的宽高以及整体自定义view宽高,所以本身也是需要在onMeasure中写的)
再来绘制五颗星
好,一颗星我们画完了,那么5颗星就for循环呗
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//绘制底部灰色星(未选中的)
for (i in 0 until 5) {
canvas.drawBitmap(
staredBitmap,
i * mStarImgWidth.toFloat(),//这里要记得每颗星星要向右偏移,否则5颗星星就重合了
0f,
paint
)
}
}
效果图
绘制背景的5颗灰色的星星也是一样的思路,只是需要先绘制5颗灰色星星,再绘制N颗黄色星星,代码就不贴了
问题来了
但产品要求评分要精确到小数,所以问题来了,当分数为2.7,那么那0.7分的残缺星星该怎么画呢
此时需要用到一个方法:canvas.clipRect(int left, int top, int right, int bottom),该方法是用来裁剪绘制区域的,具体用法我就不赘述了,大家参考这篇博客吧,作者讲得还挺详细的https://www.jianshu.com/p/550d85419121
绘制残缺星星的代码
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
val fractional = 0.7f
//裁剪半颗星
canvas.clipRect(0, 0, (mStarImgWidth*fractional).toInt(), staredBitmap.height)
canvas.drawBitmap(staredBitmap, left.toFloat(), 0f, paint)
}
效果图
哈哈,到此你们肯定就能够实现如何绘制2.7分的评分了,无非就是以下三步
- 绘制5颗灰色星星
- 绘制2颗黄色星星
- 绘制1颗裁剪0.7倍的黄色星星
在源码中有一处小小的优化,就是灰色星星不用绘制5颗,只需要绘制黄色星星没覆盖的地方,避免浪费
具体代码如下:
好了,现在贴一下目前的代码和效果图
val totalScore=5 //总分写死为5分
val score=2.7f //评分写死为2.7分
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//绘制底部灰色星(未选中的)
for (i in score.toInt() until totalScore) {
canvas.drawBitmap(
notStarBitmap, i * (mStarImgWidth.toFloat()), 0f, paint
)
}
val fractional = score-score.toInt()//2.7分:代表残缺星星的评分
//绘制黄色星(选中的整颗星)
for (i in 0 until score.toInt()) {
canvas.drawBitmap(
staredBitmap, i * (mStarImgWidth.toFloat()), 0f, paint
)
}
//计算绘制的左侧位置和右侧位置
val left =
paddingStart + score.toInt() * (mStarImgWidth.toFloat()).toInt()
val right = left + (mStarImgWidth * fractional).toInt()
//裁剪半颗星
canvas.clipRect(left, 0, right, staredBitmap.height)
canvas.drawBitmap(staredBitmap, left.toFloat(), 0f, paint)
}
效果图
实现滑动评分效果
在看代码之前先看一张说明图
解释:
- paddingStart:就是官方的android:paddingStart属性,代表左边距
- mStarImgWidth:星星的宽度
- mRatingPadding:两颗星星的左右间距
- mStarImgWidth加mRatingPadding作为一个整体,我将其称为:控件块,代码中的变量名叫做:chunkWidth,(起名字真是个麻烦的事情)
具体逻辑代码
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!mIsSupportDrag) {
return super.onTouchEvent(event)
}
//将星星和间距作为一组控件块
val chunkWidth = mStarImgWidth + mRatingPadding
//计算出包含多少个控件块,也就是占多少颗星,多少分
var newCount = ((event.x - paddingStart.toFloat()) / chunkWidth)
//计算出多滑出的百分比(一组控件块的)
val starPaddingPercent: Float = (newCount - newCount.toInt())
//计算出多滑出的百分比(一颗星的)
var starPercent: Float = chunkWidth * starPaddingPercent / mStarImgWidth
//将一颗星的百分比强制限制到1也就是100%
if (starPercent > 1) {
starPercent = 1f
}
//加上滑出的百分比,得出新的分数
newCount = newCount.toInt() + starPercent
//最后根据步长类型,调整分数
newCount = adjustRatingSelectedCount(newCount)
if (mSelectedCount != newCount) {
onRatingChangeListener(newCount)
}
mSelectedCount = newCount
invalidate()
return true
}
算法讲解
what?算法讲解是不可能讲解的,这辈子都不可能讲解(主要是我表达能力有限,容易让你们失去阅读兴趣,干扰你们思路~)
掘金地址:https://juejin.im/post/6893443087679684615/
Related Skills
node-connect
351.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
