自定义类似RatingBar打分控件

 相较于ratingbar 控件大小无法改变,该控件可设置图标大小,颜色。

package com.hedou.flowable.view

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import com.hedou.flowable.R
import com.hedou.provider.extend.getAttrInt
import kotlin.math.roundToInt

class ScopeView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet, defStyleAttr: Int = -1
) : LinearLayout(context, attrs, defStyleAttr) {

    /** 满图图标 */
    private var mFullDrawable: Drawable? = null

    /** 空图图标 */
    private var mEmptyDrawable: Drawable? = null

    /** 半图图标(可选) */
    private var mHalfDrawable: Drawable? = null

    /** 图的宽度 */
    private var mWidth = 0f

    /** 图的高度 */
    private var mHeight = 0f

    /** 图之间的间距 */
    private var mPadding = 0f

    /** 图的总数量 */
    var mTotal = 0
        set(value) {
            if (value != field) {
                field = value
            }
            requestLayout()
            initView()
            setScope(mScope)
        }

    /** 当前评分 */
    private var mScope = 0f

    /** 满图的默认颜色 */
    private var mFullColor = 0

    /** 空图的默认颜色 */
    private var mEmptyColor = 0

    /** 图边界列表 */
    private val mBoundaryList = mutableListOf<Int>()

    /** 评分变化监听器 */
    private var onScopeChangedListener: OnScopeChangedListener? = null

    /** 手动触摸更改评分监听器 */
    private var onTouchScopeChangedListener: OnTouchScopeChangedListener? = null

    init {
        orientation = HORIZONTAL
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScopeView)
        val defaultImage = ContextCompat.getDrawable(context, R.drawable.ic_flowable_star)
        mFullDrawable = typedArray.getDrawable(R.styleable.ScopeView_fullImage) ?: defaultImage
        mEmptyDrawable = typedArray.getDrawable(R.styleable.ScopeView_emptyImage) ?: defaultImage
        mHalfDrawable = typedArray.getDrawable(R.styleable.ScopeView_halfImage)
        mWidth = typedArray.getDimension(R.styleable.ScopeView_imageWidth, 24f)
        mHeight = typedArray.getDimension(R.styleable.ScopeView_imageHeight, 24f)
        mPadding = typedArray.getDimension(R.styleable.ScopeView_imagePadding, 0f)
        mTotal = typedArray.getInt(R.styleable.ScopeView_size, 5)
        mScope = typedArray.getFloat(R.styleable.ScopeView_scope, 0f)
        mFullColor = typedArray.getColor(R.styleable.ScopeView_fullColor, context.getAttrInt(R.attr.colorWarn)) // 设置满图默认颜色
        mEmptyColor = typedArray.getColor(R.styleable.ScopeView_emptyColor, context.getAttrInt(R.attr.colorRootBackground)) // 设置空图默认颜色
        typedArray.recycle()
        initView()
        setScope(mScope)
    }

    private fun initView() {
        removeAllViews()
        for (i in 0 until mTotal) {
            val imageView = ImageView(context).apply {
                layoutParams = LayoutParams(mWidth.roundToInt(), mHeight.roundToInt()).apply {
                    if (i != 0) setMargins(mPadding.roundToInt(), 0, 0, 0)
                }
                setImageDrawable(mEmptyDrawable)
                setColorFilter(mEmptyColor, PorterDuff.Mode.SRC_IN) // 设置空图颜色
            }
            addView(imageView)
        }
    }

    private fun calculateScope(x: Float): Float {
        Log.e("TAG", "calculateScope: $x")
        // 遍历每个图的边界,找到 x 所在的区间
        for (i in 0 until mTotal - 1) {
            if (x >= mBoundaryList[i] && x < mBoundaryList[i + 1]) {
                // 判定是半图还是全图
                return if (mHalfDrawable != null && x < (mBoundaryList[i] + mBoundaryList[i + 1]) / 2) {
                    i + 0.5f  // 半图
                } else {
                    i + 1f  // 全图
                }
            }
        }

        // 如果 x 超过最后一个图的边界
        return if (x >= mBoundaryList.last()) {
            if (x < mBoundaryList.last() + mWidth / 2 && mHalfDrawable != null) {
                mTotal - 0.5f  // 最后一个半图
            } else {
                mTotal.toFloat()  // 最后一个全图
            }
        } else {
            0f  // 在第一个图之前,返回全图
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (isEnabled && mBoundaryList.isNotEmpty() && event.action != MotionEvent.ACTION_UP) {
            val scope = calculateScope(event.x)
            // 通知评分变化监听器
            onTouchScopeChangedListener?.onScopeChanged(scope)
            //设置图标
            setScope(scope)
            if (event.action == MotionEvent.ACTION_DOWN) return true
        }
        return super.onTouchEvent(event)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        Log.e("TAG", "onLayout: $mBoundaryList")
        if (mBoundaryList.isEmpty()) {
            for (index in 0 until mTotal) {
                val boundary = if (index == 0) {
                    0
                } else {
                    mBoundaryList[index - 1] + mWidth.roundToInt() + mPadding.roundToInt()
                }
                mBoundaryList.add(boundary)
            }
            Log.e("TAG", "onLayout: $mBoundaryList")
        }
    }

    fun setOnScopeChangedListener(listener: OnScopeChangedListener?) {
        onScopeChangedListener = listener
    }

    fun setOnTouchScopeChangedListener(listener: OnTouchScopeChangedListener?) {
        onTouchScopeChangedListener = listener
    }

    fun setScope(scope: Float) {
        Log.e("TAG", "setScope: $scope")
        // 将评分限制在最大评分值 mTotal 以内
        val rating = scope.coerceAtMost(mTotal.toFloat())
        mScope = rating
        onScopeChangedListener?.onScopeChanged(scope)
        // 将评分分为整数部分和小数部分
        val partInteger = rating.toInt()
        val partDecimal = rating - partInteger

        // 遍历所有图并设置其状态
        for (i in 0 until mTotal) {
            val imageView = getChildAt(i) as ImageView
            when {
                i < partInteger -> {
                    imageView.setImageDrawable(mFullDrawable)
                    imageView.setColorFilter(mFullColor, PorterDuff.Mode.SRC_IN) // 设置满图颜色
                }

                i == partInteger -> {
                    when {
                        partDecimal >= 0.75f -> {
                            imageView.setImageDrawable(mFullDrawable)
                            imageView.setColorFilter(mFullColor, PorterDuff.Mode.SRC_IN) // 设置满图颜色
                        }

                        partDecimal >= 0.25f && mHalfDrawable != null -> {
                            imageView.setImageDrawable(mHalfDrawable)
                            imageView.clearColorFilter() // 不设置颜色滤镜,使用原图颜色
                        }

                        else -> {
                            imageView.setImageDrawable(mEmptyDrawable)
                            imageView.setColorFilter(mEmptyColor, PorterDuff.Mode.SRC_IN) // 设置空图颜色
                        }
                    }
                }

                else -> {
                    imageView.setImageDrawable(mEmptyDrawable)
                    imageView.setColorFilter(mEmptyColor, PorterDuff.Mode.SRC_IN) // 设置空图颜色
                }
            }
        }
    }

    /** 评分变化监听器接口 */
    interface OnScopeChangedListener {
        fun onScopeChanged(scope: Float)
    }

    /** 触摸评分变化监听器接口 */
    interface OnTouchScopeChangedListener {
        fun onScopeChanged(scope: Float)
    }
}
<resources>
    <declare-styleable name="ScopeView">
        <attr name="imageWidth" format="dimension" />
        <attr name="imageHeight" format="dimension" />
        <attr name="imagePadding" format="dimension" />
        <attr name="fullImage" format="reference" />
        <attr name="emptyImage" format="reference" />
        <attr name="halfImage" format="reference" />
        <attr name="fullColor" format="color" />
        <attr name="emptyColor" format="color" />
        <attr name="size" format="integer" />
        <attr name="scope" format="float" />
        <attr name="stepSize" format="float" />
    </declare-styleable>
</resources>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值