首先了解下, 自定义View的三部曲.
此方法主要目的, 就是根据xml的
android:layout_width="wrap_content"
android:layout_height="wrap_content"
wrap_content
match_parent
这2个属性, 来确定测量自身的大小.
当然, 这2个值, 只是parent
告诉你, 需要按照此规则来测量, 如果你是一个坏孩子, 那么可以无视测量规则, 任意设置一个宽度和高度, 比如: setMeasuredDimension(1万, 2万)
就是如此简单;
如果你是自定义View, 此方法可以不必override
如果你是自定义的ViewGroup, 那么就必须override
, 此方法的目的就是由你决定child view在界面上的位置.
在这个方法里面, 你可以展开你天才般的做图功能, 想画啥就画啥. 美美的view, 就这样出来了.
友情提示:如果你是自定义的ViewGroup, 还需要调用setWillNotDraw(false)
方法, 否则onDraw
方法不会执行哦
开始本文:
拿到需求, 先分析一波, 不着急上手. 因为你的分析步骤, 严重影响了后续的开发效率.
分析的越细, 实现越轻松.
1: 星星有2种状态, 普通和高亮, 可以用2个drawable成员变量搞定
2: 星星的个数是个变化的值, 可以用一个成员变量搞定
3: 星星之间的间隙, 可以用一个成员变量搞定
很简单的需求, 所以步骤不多. 总之, 问题就是靠每一个步骤解答出来的.
class RRatingBar(context: Context, attributeSet: AttributeSet? = null) : View(context, attributeSet) {
}
声明2个成员变量, 保存2种星星的状态
/*五角星 选中的 图案*/
var ratingSelectorDrawable by LayoutProperty(getDrawable(R.drawable.base_wujiaoxing_20))
/*五角星 未选中的 图案*/
var ratingNormalDrawable by LayoutProperty(getDrawable(R.drawable.base_wujiaoxing_20_n))
//这个属性代理的作用, 就是在属性被赋值的时候, 自动调用`requestLayout()`方法
class LayoutProperty<T>(var value: T) : ReadWriteProperty<View, T> {
override fun getValue(thisRef: View, property: KProperty<*>): T = value
override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
this.value = value
thisRef.requestLayout()
}
}
声明星星的数量
/**星星的数量*/
var ratingNum: Int by RefreshProperty(5)
同样用了一个属性代理postInvalidate()
:
/*定义一个自动刷新的属性代理*/
class RefreshProperty<T>(var value: T) : ReadWriteProperty<View, T> {
override fun getValue(thisRef: View, property: KProperty<*>): T = value
override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
this.value = value
thisRef.postInvalidate()
}
}
声明星星的间隙
/**星星之间的间隙*/
var ratingSpace: Float by RefreshProperty(8 * density)
到这里, 其实就已经可以绘制出星星了
override fun onDraw(canvas: Canvas) {
//调用此方法, 不禁用view本身的功能, 比如:原本的background属性
super.onDraw(canvas)
//确保星星的有效数量
val num = Math.max(0, Math.min(curRating, ratingNum))
//绘制未选中星星
var left = paddingLeft
for (i in num until ratingNum) {
canvas.save()
canvas.translate(left + i * ratingWidth + i * ratingSpace, paddingTop.toFloat())
ratingNormalDrawable.draw(canvas)
canvas.restore()
}
//绘制选中星星
left = paddingLeft
for (i in 0 until num) {
canvas.save()
canvas.translate(left + i * ratingWidth + i * ratingSpace, paddingTop.toFloat())
ratingSelectorDrawable.draw(canvas)
canvas.restore()
}
}
但是, 这个时候还没有处理手势touch事件. 所以星星不能响应手势操作.
根据手势的x坐标 和 每个星星的中心点x的左边, 如果手势x大于星星x, 那么肯定选中了.以此类推, 找到最后一个星星.
override fun onTouchEvent(event: MotionEvent): Boolean {
val action = MotionEventCompat.getActionMasked(event)
when (action) {
MotionEvent.ACTION_DOWN -> {
parent.requestDisallowInterceptTouchEvent(true)
calcRating(event.x)
}
MotionEvent.ACTION_MOVE -> {
calcRating(event.x)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
parent.requestDisallowInterceptTouchEvent(false)
}
}
return true
}
/*根据x的坐标, 计算星星的个数*/
private fun calcRating(x: Float) {
var left = paddingLeft
var index = 0
for (i in 0 until ratingNum) {
if (x > left + i * ratingWidth + i * ratingSpace) {
index = i + 1
}
}
if (index.minValue(minRating) != curRating) {
curRating = index
postInvalidate()
}
}
请使用QQ扫码加群, 小伙伴们在等着你哦!
关注我的公众号, 每天都能一起玩耍哦!