PercentRatingBar

Introduction: 百分比评分控件
More: Author   ReportBugs   
Tags:

按滑动百分比来评分的控件

依赖方式

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 分,并且星星的评分样式也要与分值完全对应 也就是要实现这种效果

我一听就懵逼了,这不是为难我么

不过既然产品既然提了需求,咱也得尽量去实现,否则以后还怎么愉快玩耍

需求描述

  1. 支持整颗星、半颗星和按百分比评分
  2. 支持滑动和点击评分
  3. 支持自定义星星图标和星星间距
  4. 支持...好了闭嘴吧...咱都给你实现了

需求分析

  1. 首先绘制星星很简单,调用 canvas.drawBitmap 就可以,多个星星 for 循环绘制即可
  2. 复杂的地方有两处: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/

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools