相较于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>