Android自定义控件入门 07 自定义RatingBar

131 阅读2分钟

分析: 交互控件(用户触摸)

1.刚进来初始化的样子

1.1 其实就是绘制5张没有选中的图片

自定义属性

  1. 2张图片资源
  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_DOWNMotionEvent.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  
        }  
    }