Android自定义View第五弹(可滑动的星星评价)

个人开发中自定义View系列(有需要的可以点击查看收藏)

  1. Android自定义view第一弹(防小米计步)
  2. Android自定义View第二弹(旋转的体重)
  3. Android自定义View第三弹(反人类尺子)
  4. Android自定义View第四弹(Kotlin流式布局)

距离上一篇自定义view已经过去了一年多了,这次主要给大家介绍的是可滑动的星星评价,虽然Google官方也提供了 RatingBar 但是没办法满足我的需要只能自己定义一个了,废话不多说先上图:
在这里插入图片描述
这个选中以及默认的心型都是UI提供的图片,上代码:

1.自定义view的代码

import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import cn.neoclub.uki.R
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.roundToInt

/**
 * Author: Mr.Dong
 * Date: 2022/2/15 4:31 下午
 * Description: 点击心心评价
 */
class HeartRatingBar : View {
    private var starDistance = 0 //星星间距
    private var starCount = 5 //星星个数
    private var starSize = 0 //星星高度大小,星星一般正方形,宽度等于高度
    private var starMark = 0 //评分星星
    private var starFillBitmap: Bitmap? = null //亮星星
    private var starEmptyDrawable : Drawable? = null//暗星星
    private var onStarChangeListener : OnStarChangeListener? = null//监听星星变化接口
            
    private var paint : Paint? = null//绘制星星画笔
    //是否显示整数的星星       
    private var integerMark = false
    //初始化可以被定义为滑动的距离(超过这个距离就是滑动,否则就是点击事件)
    private var scaledTouchSlop:Int=0

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        init(context, attrs)
    }

    /**
     * 初始化UI组件
     *
     * @param context
     * @param attrs
     */
    private fun init(context: Context, attrs: AttributeSet?) {
        //获取滑动的有效距离
        scaledTouchSlop=ViewConfiguration.get(context).scaledTouchSlop
        isClickable = true
        //获取各种属性的值
        val mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.HeartRatingBar)
        starDistance = mTypedArray.getDimension(R.styleable.HeartRatingBar_starDistance, 0f).toInt()
        starSize = mTypedArray.getDimension(R.styleable.HeartRatingBar_starSize, 20f).toInt()
        starCount = mTypedArray.getInteger(R.styleable.HeartRatingBar_starCount, 5)
        starEmptyDrawable = mTypedArray.getDrawable(R.styleable.HeartRatingBar_starEmpty)
        starFillBitmap = drawableToBitmap(mTypedArray.getDrawable(R.styleable.HeartRatingBar_starFill))
        mTypedArray.recycle()
        paint = Paint()
        //设置抗锯齿
        paint?.isAntiAlias = true
        //设置渲染器
        paint?.shader = BitmapShader(starFillBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    }

    /**
     * 设置是否需要整数评分
     * @param integerMark
     */
    fun setIntegerMark(integerMark: Boolean) {
        this.integerMark = integerMark
    }

    /**
     * 设置显示的星星的分数
     *
     * @param mark
     */
    private fun setStarMark(mark: Int) {
        starMark = if (integerMark) {
            //ceil函数 去除小数点后面的 返回 double 类型,返回值大于或等于给定的参数 例Math.ceil(100.675) = 101.0
            ceil(mark.toDouble()).toInt()
        } else {
            (mark * 10).toFloat().roundToInt() * 1 / 10
        }
        if (onStarChangeListener != null) {
            onStarChangeListener?.onStarChange(starMark) //调用监听接口
        }
        invalidate()
    }

    /**
     * 获取显示星星的数目
     *
     * @return starMark
     */
    fun getStarMark(): Int {
        return starMark
    }

    /**
     * 定义星星点击的监听接口
     */
    interface OnStarChangeListener {
        fun onStarChange(mark: Int)
    }

    /**
     * 设置监听
     * @param onStarChangeListener
     */
    fun setOnStarChangeListener(onStarChangeListener: OnStarChangeListener?) {
        this.onStarChangeListener = onStarChangeListener
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        //设置view的宽度和高度 继承view必须重写此方法
        setMeasuredDimension(starSize * starCount + starDistance * (starCount - 1), starSize)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (starFillBitmap == null || starEmptyDrawable == null) {
            return
        }
        //绘制空的星星
        for (i in 0 until starCount) {
            //设置starEmptyDrawable绘制的长方形区域,当调用draw()方法后就可以直接绘制
            starEmptyDrawable?.setBounds(
                (starDistance + starSize) * i,
                0,
                (starDistance + starSize) * i + starSize,
                starSize
            )
            starEmptyDrawable?.draw(canvas)
        }
        if (starMark > 1) {
            //绘制了第一个star
            canvas.drawRect(0f, 0f, starSize.toFloat(), starSize.toFloat(), paint!!)
            if (starMark - starMark == 0) { //第一步必走这里 
                //绘制亮星星
                for (i in 1 until starMark) {
                    //每次位移start的宽度+间距
                    canvas.translate((starDistance + starSize).toFloat(), 0f)
                    canvas.drawRect(0f, 0f, starSize.toFloat(), starSize.toFloat(), paint!!)
                }
            } else { //非整形的star绘制走这里
                for (i in 1 until starMark - 1) {
                    canvas.translate((starDistance + starSize).toFloat(), 0f)
                    canvas.drawRect(0f, 0f, starSize.toFloat(), starSize.toFloat(), paint!!)
                }
                canvas.translate((starDistance + starSize).toFloat(), 0f)
                canvas.drawRect(
                    0f,
                    0f,
                    starSize * (((starMark - starMark) * 10).toFloat().roundToInt() * 1.0f / 10),
                    starSize.toFloat(),
                    paint!!
                )
            }
        } else {
            //startMark=0 啥都没绘制
            canvas.drawRect(0f, 0f, (starSize * starMark).toFloat(), starSize.toFloat(), paint!!)
        }
    }

    //记录一下上次down的x的位置
    private var downX:Int=0

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var x = event.x.toInt()
        if (x < 0) x = 0
        if (x > measuredWidth) x = measuredWidth
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downX=x
                //对于除数不能为0的限制
                if(starCount==0||(measuredWidth * 1 / starCount)==0){
                    return false
                }
                val count=x * 1 / (measuredWidth * 1 / starCount)
                setStarMark(count+1)
            }
            MotionEvent.ACTION_MOVE -> {
                //当滑动距离的绝对值小于官方定义的有效滑动距离则不走move当做down处理
                if(abs(event.x-downX)<scaledTouchSlop){
                    return false
                }
                if(starCount==0||(measuredWidth * 1 / starCount)==0){
                    return false
                }
                setStarMark(x * 1 / (measuredWidth * 1 / starCount))
            }
            MotionEvent.ACTION_UP -> {}
        }
        invalidate()
        return super.onTouchEvent(event)
    }

    /**
     * drawable转bitmap
     *
     * @param drawable
     * @return
     */
    private fun drawableToBitmap(drawable: Drawable?): Bitmap? {
        if (drawable == null) return null
        val bitmap = Bitmap.createBitmap(starSize, starSize, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        drawable.setBounds(0, 0, starSize, starSize)
        drawable.draw(canvas)
        return bitmap
    }
}

2.自定义View的使用

    <cn.neoclub.uki.message.widget.HeartRatingBar
        android:id="@+id/rb_rating_bar"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        app:starCount="5"
        app:starDistance="7dp"
        app:starEmpty="@drawable/icon_heart_rating_default"
        app:starFill="@drawable/icon_heart_rating_select"
        app:starSize="40dp" />

3.attrs.xml文件中的属性

 <declare-styleable name="HeartRatingBar">
        <attr name="starDistance" format="dimension"/>
        <attr name="starSize" format="dimension"/>
        <attr name="starCount" format="integer"/>
        <attr name="starEmpty" format="reference"/>
        <attr name="starFill" format="reference"/>
    </declare-styleable>

4.送你两张图,怕你运行不起来

1.icon_heart_rating_select.png
在这里插入图片描述
2.icon_heart_rating_default.png
在这里插入图片描述
是不是感觉这边少了个icon,对了就是少一张😄(其实是有图的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值