按滑动百分比来评分的控件
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:如果在滑动时,将滑动位置转化为分数
不过没关系,我们可以换个改为在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?算法讲解是不可能讲解的,这辈子都不可能讲解(主要是我表达能力有限,容易让你们失去阅读兴趣,干扰你们思路~)