效果图

1.在res下values添加attr.xml
XML
<declare-styleable name="CustomRatingBar">
<attr name="starCount" format="integer" /><!-- 总共有几颗星-->
<attr name="rating" format="float" /><!-- 默认选择几颗星-->
<attr name="starSpacing" format="dimension" /><!--padding值-->
<attr name="starEmpty" format="reference" /><!--选中后的图片-->
<attr name="starFull" format="reference" /><!--未选中时的图片-->
</declare-styleable>
2.自定义View(支持手势左右滑动选中)
Kotlin
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import android.widget.ImageView
import android.widget.LinearLayout
class CustomRatingBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private var starCount = 5
var currentRating = 0f
var mRating = 0f
private var starSpacing = 8
private val starViews = mutableListOf<ImageView>()
init {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
// 解析 XML 中的自定义属性
context.obtainStyledAttributes(attrs, R.styleable.CustomRatingBar).apply {
starCount = getInt(R.styleable.CustomRatingBar_starCount, 5)
currentRating = getFloat(R.styleable.CustomRatingBar_rating, 0f)
mRating = getFloat(R.styleable.CustomRatingBar_rating, 0f)
starSpacing = getDimensionPixelSize(R.styleable.CustomRatingBar_starSpacing, 8)
recycle()
}
initStars()
}
private fun initStars() {
removeAllViews()
for (i in 0 until starCount) {
val star = ImageView(context).apply {
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
if (i > 0) leftMargin = starSpacing
}
setOnClickListener { setRating((i + 1).toFloat()) }
}
addView(star)
starViews.add(star)
}
updateStars()
}
fun setRating(rating: Float) {
currentRating = rating.coerceIn(mRating, starCount.toFloat())
updateStars()
}
private fun updateStars() {
starViews.forEachIndexed { index, imageView ->
//向左滑动最少显示指定的默认选中数量
val resId = if (index < currentRating || (index==0&¤tRating==mRating)) R.mipmap.star_true else R.mipmap.star_false
imageView.setImageResource(resId)
}
}
// 支持手指滑动评分
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
val newRating = (event.x / width * starCount).toInt() + 1
setRating(newRating.toFloat())
return true
}
}
return super.onTouchEvent(event)
}
}
3.xml中使用
XML
<LinearLayout
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="15dp"
android:text="星级评分:"/>
<CustomRatingBar
android:id="@+id/customRatingBar"
android:layout_width="wrap_content"
android:layout_height="50dp"
app:starCount="5"
app:rating="1"
app:starSpacing="12dp"
app:starEmpty="@mipmap/star_true"
app:starFull="@mipmap/star_false" />
</LinearLayout>
4.代码中获取当前选中几颗星
XML
val choose = binding.customRatingBar.currentRating.toInt()
图片元素(效果图中使用的图片,可替换自己的)
