Kotlin-->自定义评分控件RatingBar

这里写图片描述

首先了解下, 自定义View的三部曲.

1:onMeasure方法

此方法主要目的, 就是根据xml的
android:layout_width="wrap_content"
android:layout_height="wrap_content"

wrap_content match_parent 这2个属性, 来确定测量自身的大小.
当然, 这2个值, 只是parent告诉你, 需要按照此规则来测量, 如果你是一个坏孩子, 那么可以无视测量规则, 任意设置一个宽度和高度, 比如: setMeasuredDimension(1万, 2万) 就是如此简单;

2:onLayout方法

如果你是自定义View, 此方法可以不必override
如果你是自定义的ViewGroup, 那么就必须override, 此方法的目的就是由你决定child view在界面上的位置.

3:onDraw方法

在这个方法里面, 你可以展开你天才般的做图功能, 想画啥就画啥. 美美的view, 就这样出来了.
友情提示:如果你是自定义的ViewGroup, 还需要调用setWillNotDraw(false)方法, 否则onDraw方法不会执行哦


开始本文:

拿到需求, 先分析一波, 不着急上手. 因为你的分析步骤, 严重影响了后续的开发效率.
分析的越细, 实现越轻松.

1: 星星有2种状态, 普通和高亮, 可以用2个drawable成员变量搞定
2: 星星的个数是个变化的值, 可以用一个成员变量搞定
3: 星星之间的间隙, 可以用一个成员变量搞定

很简单的需求, 所以步骤不多. 总之, 问题就是靠每一个步骤解答出来的.

1:自定义一个RRatingBar 继承自 View

class RRatingBar(context: Context, attributeSet: AttributeSet? = null) : View(context, attributeSet) {
}

声明2个成员变量, 保存2种星星的状态

    /*五角星 选中的 图案*/
    var ratingSelectorDrawable by LayoutProperty(getDrawable(R.drawable.base_wujiaoxing_20))

    /*五角星 未选中的 图案*/
    var ratingNormalDrawable by LayoutProperty(getDrawable(R.drawable.base_wujiaoxing_20_n))

kotlin 属性代理的使用

//这个属性代理的作用, 就是在属性被赋值的时候, 自动调用`requestLayout()`方法
class LayoutProperty<T>(var value: T) : ReadWriteProperty<View, T> {
    override fun getValue(thisRef: View, property: KProperty<*>): T = value

    override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
        this.value = value
        thisRef.requestLayout()
    }
}

声明星星的数量

   /**星星的数量*/
    var ratingNum: Int by RefreshProperty(5)

同样用了一个属性代理postInvalidate():

/*定义一个自动刷新的属性代理*/
class RefreshProperty<T>(var value: T) : ReadWriteProperty<View, T> {
    override fun getValue(thisRef: View, property: KProperty<*>): T = value

    override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
        this.value = value
        thisRef.postInvalidate()
    }
}

声明星星的间隙

    /**星星之间的间隙*/
    var ratingSpace: Float by RefreshProperty(8 * density)

2:onDraw

到这里, 其实就已经可以绘制出星星了

  override fun onDraw(canvas: Canvas) {
       //调用此方法, 不禁用view本身的功能, 比如:原本的background属性
       super.onDraw(canvas)

       //确保星星的有效数量
       val num = Math.max(0, Math.min(curRating, ratingNum))

       //绘制未选中星星
       var left = paddingLeft
       for (i in num until ratingNum) {
           canvas.save()
           canvas.translate(left + i * ratingWidth + i * ratingSpace, paddingTop.toFloat())
           ratingNormalDrawable.draw(canvas)
           canvas.restore()
       }

       //绘制选中星星
       left = paddingLeft
       for (i in 0 until num) {
           canvas.save()
           canvas.translate(left + i * ratingWidth + i * ratingSpace, paddingTop.toFloat())
           ratingSelectorDrawable.draw(canvas)
           canvas.restore()
       }
   }

但是, 这个时候还没有处理手势touch事件. 所以星星不能响应手势操作.

3:onTouchEvent手势

根据手势的x坐标 和 每个星星的中心点x的左边, 如果手势x大于星星x, 那么肯定选中了.以此类推, 找到最后一个星星.

 override fun onTouchEvent(event: MotionEvent): Boolean {
        val action = MotionEventCompat.getActionMasked(event)
        when (action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
                calcRating(event.x)
            }
            MotionEvent.ACTION_MOVE -> {
                calcRating(event.x)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                parent.requestDisallowInterceptTouchEvent(false)
            }
        }
        return true
    }

    /*根据x的坐标, 计算星星的个数*/
    private fun calcRating(x: Float) {
        var left = paddingLeft
        var index = 0
        for (i in 0 until ratingNum) {
            if (x > left + i * ratingWidth + i * ratingSpace) {
                index = i + 1
            }
        }
        if (index.minValue(minRating) != curRating) {
            curRating = index
            postInvalidate()
        }
    }

联系作者

请使用QQ扫码加群, 小伙伴们在等着你哦!

关注我的公众号, 每天都能一起玩耍哦!

猜你喜欢

转载自blog.csdn.net/angcyo/article/details/78027614