Kotlin-->自定义评分控件RatingBar
首先了解下, 自定义View的三部曲.
1:onMeasure方法
此方法主要目的, 就是根据xml的
android:layout_width="wrap_content"
android:layout_height="wrap_content"
wrap_content
match_parent
这2个属性, 来确定测量自身的大小.
当然, 这2个值, 只是parent
告诉你, 需要按照此规则来测量, 如果你是一个坏孩子, 那么可以无视测量规则, 任意设置一个宽度和高度, 比如: setMeasuredDimension(1万, 2万)
就是如此简单;
2:onLayout方法
如果你是自定义View, 此方法可以不必override
如果你是自定义的ViewGroup, 那么就必须override
, 此方法的目的就是由你决定child view在界面上的位置.
3:onDraw方法
在这个方法里面, 你可以展开你天才般的做图功能, 想画啥就画啥. 美美的view, 就这样出来了.
友情提示:如果你是自定义的ViewGroup, 还需要调用setWillNotDraw(false)
方法, 否则onDraw
方法不会执行哦
开始本文:
拿到需求, 先分析一波, 不着急上手. 因为你的分析步骤, 严重影响了后续的开发效率.
分析的越细, 实现越轻松.
1: 星星有2种状态, 普通和高亮, 可以用2个drawable成员变量搞定
2: 星星的个数是个变化的值, 可以用一个成员变量搞定
3: 星星之间的间隙, 可以用一个成员变量搞定
很简单的需求, 所以步骤不多. 总之, 问题就是靠每一个步骤解答出来的.
1:自定义一个RRatingBar 继承自 View
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))
kotlin 属性代理的使用
//这个属性代理的作用, 就是在属性被赋值的时候, 自动调用`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)
2:onDraw
到这里, 其实就已经可以绘制出星星了
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事件. 所以星星不能响应手势操作.
3:onTouchEvent手势
根据手势的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扫码加群, 小伙伴们在等着你哦!
关注我的公众号, 每天都能一起玩耍哦!
更多相关文章
- Android文件系统的结构及目录用途、操作方法
- 屏幕旋转会重启onCreate方法
- Android ADT 找不到Annotation Processing选项,解决方法。
- 进程方法Android进程与线程基本知识
- Android~两种将Activity设置成窗口样式的方法
- [Android Pro] Android的Animation之LayoutAnimation使用方法
- parseSdkContent failedCould not initialize class android.gra
- [转]android editText属性详细介绍
- Android系列之Intent传递对象的两种方法