Android自定义View第五弹(可滑动的星星评价)
距离上一篇自定义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,对了就是少一张😄(其实是有图的)
作者:小翘_上海
链接:https://juejin.cn/post/7067093473492467742
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。