分析: 交互控件(用户触摸)
1.刚进来初始化的样子
1.1 其实就是绘制5张没有选中的图片
自定义属性
- 2张图片资源
- 评分的等级数量
<declare-styleable name="SpecialRattingBar">
<attr name="star_Normal" format="reference" />
<attr name="star_Select" format="reference" />
<attr name="star_Number" format="integer" />
</declare-styleable>
1.2 指定控件的宽高一个小的任务,自己实现padding,星星之间的间隔
1.2.1 onMeasure()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val height = starNormalBitmap.height + paddingTop + paddingBottom
val weight = (starNormalBitmap.width + paddingLeft) * starNumber
setMeasuredDimension(weight, height)
}
1.2.2 onDraw()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
for (i in 0 until starNumber) {
//i*星星的宽度,原理:画图一目了然
val left = (i * starNormalBitmap.width).toFloat()+paddingLeft*(i+1)
val top = paddingTop.toFloat()
canvas.drawBitmap(starNormalBitmap, left, top, null)
}
}
2.处理用户交互部分(onTouch())
| event.getX() | 相对于当前控件的位置 |
|---|---|
| event.getRawX() | 获取屏幕的x位置 |
2.1.判断手指的位置
val moveX=event.x
2.2根据当前位置计算出分数
var currentStar = ((moveX / (starNormalBitmap.width+paddingLeft)) + 1).toInt()
if (currentStar < 0) {
currentStar = 0
}
if (currentStar > starNumber) {
currentStar = starNumber
}
2.3.刷新显示
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
for (i in 0 until starNumber) {
//i*星星的宽度,原理:画图一目了然
val left = (i * starNormalBitmap.width).toFloat() + paddingLeft * (i + 1)
val top = paddingTop.toFloat()
val isSelected = (i + 1) <= currentStarNumber
val bitmap = (if (isSelected) starSelectBitmap else starNormalBitmap)
canvas.drawBitmap(bitmap, left, top, null)
}
}
3.滑动没效果?
onTouchEvent()返回值改为return true
override fun onTouchEvent(e: MotionEvent): Boolean {
//...
return true
}
3.1 原理
classDiagram
ViewGroup --|> ViewGroup1
ViewGroup1 --|> ViewGroup2
ViewGroup2 --|> child_dispatchTouchEvent
ViewGroup : public boolean dispatchTouchEvent(MotionEvent ev)
ViewGroup : 父布局
class ViewGroup1{
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
}
class ViewGroup2{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits)
}
class child_dispatchTouchEvent{
child.dispatchTouchEvent(event) View的方法
如果返回false的话,上面都返回false
ViewGroup的private TouchTarget addTouchTarget() 也不会赋值
}
关键代码
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
4.优化
4.1 减少调用invalidate()
//分数相同的情况下不要绘制了
if (currentStar != currentStarNumber) {
currentStarNumber = currentStar
//3.刷新显示
invalidate()
}
4.2 取消监听MotionEvent.ACTION_DOWN,MotionEvent.ACTION_UP
5 完整代码
class SpecialRattingBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : View(context, attrs, defStyle) {
private val starNumber: Int
private val starNormalBitmap: Bitmap
private val starSelectBitmap: Bitmap
private var currentStarNumber=0
private val itemWeight :Int
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val height = starNormalBitmap.height + paddingTop + paddingBottom
val weight = itemWeight * starNumber
setMeasuredDimension(weight, height)
}
init {
context.obtainStyledAttributes(attrs, R.styleable.SpecialRattingBar).apply {
starNumber = getInt(R.styleable.SpecialRattingBar_star_Number, 5)
val normal = getResourceId(R.styleable.SpecialRattingBar_star_Normal, R.drawable.star_normal)
val select = getResourceId(R.styleable.SpecialRattingBar_star_Select, R.drawable.star_select)
starNormalBitmap = BitmapFactory.decodeResource(resources, normal)
starSelectBitmap = BitmapFactory.decodeResource(resources, select)
itemWeight=starNormalBitmap.width + paddingLeft
recycle()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
for (i in 0 until starNumber) {
//i*星星的宽度,原理:画图一目了然
val left = (i * starNormalBitmap.width).toFloat() + paddingLeft * (i + 1)
val top = paddingTop.toFloat()
val isSelected = ((i + 1) <= currentStarNumber)
val bitmap = (if (isSelected) starSelectBitmap else starNormalBitmap)
canvas.drawBitmap(bitmap, left, top, null)
}
}
/**
* 移动 按下 抬起 处理逻辑都是一样,
*
* 1.判断手指的位置
* 2.根据当前位置计算出分数
* 3.刷新显示
* */
override fun onTouchEvent(e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_MOVE -> {
//1.判断手指的位置
val moveX = e.x
//2.根据当前位置计算出分数
var currentStar = ((moveX / itemWeight) + 1).toInt()
if (currentStar < 0) {
currentStar = 0
}
if (currentStar > starNumber) {
currentStar = starNumber
}
//分数相同的情况下不要绘制了
if (currentStar != currentStarNumber) {
currentStarNumber = currentStar
//3.刷新显示
invalidate()
}
}
}
// return super.onTouchEvent(e)
//默认false,不消费
//第一次ACTION_DOWN,返回false,ACTION_DOWN以后的事件就进不来了
return true
}
}