Android自定义控件入门 04 仿QQ运动步数进度效果

88 阅读3分钟

1.invalidate()源码分析

p.invalidateChild(this, damage);

do{
    parent = parent.invalidateChildInParent(location, dirty);  
    if (view != null) {  
        // Account for transform on current parent  
        Matrix m = view.getMatrix();  
        if (!m.isIdentity()) {  
            RectF boundingRect = attachInfo.mTmpTransformRect;  
            boundingRect.set(dirty);  
            m.mapRect(boundingRect);  
            dirty.set((int) Math.floor(boundingRect.left),  
                (int) Math.floor(boundingRect.top),  
                (int) Math.ceil(boundingRect.right),  
                (int) Math.ceil(boundingRect.bottom));  
        }  
    }
} while (parent != null);

mView.draw(canvas);

invlidate()流程 :一路往上跑 ,跑到最外层draw()-> dispatchDraw() 一路往下画 最终画到当前调用invaldate的view的onDraw()方法

invlidate()牵连着整个layout布局中的view

image.png

为什么不能在子线程更新UI? 开了线程,更新UI一般会调用setText()setImageBitmap()setVisibility最终调到这里面来ViewRootImpl #checkThread()

void checkThread() {  
    Thread current = Thread.currentThread();  
    if (mThread != current) {  
        throw new CalledFromWrongThreadException(  
            "Only the original thread that created a view hierarchy can touch its views."  
                + " Expected: " + mThread.getName()  
                + " Calling: " + current.getName());  
    }  
}

mThread在构造函数中初始化的,为主线程MainThread

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,WindowLayout windowLayout) {  
    mThread = Thread.currentThread();
}

UI的绘制流程performTraversals()performMeasure()performLayout(),performDraw() 测量,摆放,绘制,三大流程

2.如何像WX朋友圈一样优化过度渲染

看自己界面有没有过度渲染 开发者选项 打开调试GPU过度绘制

2.1 网上的解决方案

尽量不要嵌套

能不设置背景不要设置背景

2.2 最好的解决方案

获取到数据去设置 setText(),setlmageView其实 invalidate()

最好是自己画,不要用系统的嵌套布局,运行效率高,实现功能效率低(抉择问题)

3.仿QQ运动步数进度效果

3.1 流程

  1. 分析效果:
  2. 确定自定义属性,编写attrs.xml
  3. 在布局中使用
  4. 在自定义view中获取自定义属性
  5. onMeasure()
  6. 画弧 (1.画外圆弧,2.画内圆弧,百分比,是使用者设置的,3.画文字)
  7. 其他 写几个方法动起来

3.2 自定义属性

<declare-styleable name="StepView">  
    <attr name="outCircleColor" format="color|reference" />  
    <attr name="inCircleColor" format="color|reference" />  
    <attr name="borderWidth" format="dimension" />  
    <attr name="centerTextSize" format="dimension" />  
    <attr name="centerTextColor" format="color|reference" />  
</declare-styleable>

3.3 自定义View

StepView


class StepView @JvmOverloads constructor(  
    context: Context,  
    attributeSet: AttributeSet? = null,  
    defStyleAttr: Int = 0  
) :  
    View(context, attributeSet, defStyleAttr) {  
    private val outCircleColor: Int  
    private val inCircleColor: Int  
    private val borderWidth: Int  
    private val centerTextSize: Int  
    private val centerTextColor: Int  
  
    /**  
    * 总共步数  
    * */  
    private var stepMax = 100  

    /**  
    * 当前步数  
    * */  
    private var currentStep = 45  

    init {  
        val array = context.obtainStyledAttributes(attributeSet, R.styleable.StepView)  
        inCircleColor = array.getColor(R.styleable.StepView_inCircleColor, Color.RED)  
        outCircleColor = array.getColor(R.styleable.StepView_outCircleColor, Color.BLUE)  
        centerTextColor = array.getColor(R.styleable.StepView_centerTextColor, Color.BLUE)  
        borderWidth = array.getDimension(R.styleable.StepView_borderWidth, 20F).toInt()  
        centerTextSize = array.getDimensionPixelSize(R.styleable.StepView_centerTextSize, context.sp2px(15))  
        array.recycle()  
    }  
  
    /**  
    * 5.onMeasure()  
    * */  
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)  
        //调用者在布局中可能warp_content,  
        val isWrap = (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) ||  
            (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST)  
        val size = if (isWrap) {  
            40  
        } else {  
            val width = MeasureSpec.getSize(widthMeasureSpec)  
            val height = MeasureSpec.getSize(heightMeasureSpec)  
            //宽高不一致,取最小值,确保是个正方形  
            min(width, height)  
        }  
        setMeasuredDimension(size, size)  
    }  

    /**  
    * 区域  
    * */  
    private val arcRectF by lazy {  
        //半圆边缘没显示完整的原因?:描边有宽度  
        //中心点  
        // val center = width / 2  
        // val radius = center - borderWidth/2  
        // val leftAndTop = (center - radius).toFloat()  
        val leftAndTop = (borderWidth / 2).toFloat()  
        // val rightAndBottom = (center + radius).toFloat()  
        val rightAndBottom = (width - borderWidth / 2).toFloat()  
        RectF(leftAndTop, leftAndTop, rightAndBottom, rightAndBottom)  
    }  
    private val inCirclePaint by lazy {  
        Paint().apply {  
            isAntiAlias = true  
            strokeWidth = borderWidth.toFloat()  
            color = inCircleColor  
            style = Paint.Style.STROKE  
            strokeCap = Paint.Cap.ROUND  
        }  
    }  
    private val outCirclePaint by lazy {  
        Paint().apply {  
            isAntiAlias = true  
            strokeWidth = borderWidth.toFloat()  
            color = outCircleColor  
            //画笔实心  
            style = Paint.Style.STROKE  
            strokeCap = Paint.Cap.ROUND  
        }  
    }  
    private val textPaint by lazy {  
        Paint().apply {  
            isAntiAlias = true  
            color = centerTextColor  
            textSize = centerTextSize.toFloat()  
        }  
    }  

    /**  
    * 文字的区域  
    * */  
    private val textRect by lazy { Rect() }  

    /**  
    * 6.画弧  
    * */  
    override fun onDraw(canvas: Canvas) {  
        super.onDraw(canvas)  
        // 6.1 画外圆弧  
        canvas.drawArc(arcRectF, 135F, 270F, false, outCirclePaint)  
        // 6.2 画内圆弧,百分比,是使用者设置的  
        val sweepAngle = if ((stepMax < currentStep) || stepMax == 0) {  
            0F  
        } else {  
            (currentStep.toFloat() / stepMax.toFloat()) * 270F  
        }  
        canvas.drawArc(arcRectF, 135F, sweepAngle, false, inCirclePaint)  
        // 6.3 画文字  
        val centerText = "${currentStep}/${stepMax}"  
        textPaint.getTextBounds(centerText, 0, centerText.length, textRect)  
        val dx = (width / 2 - textRect.width() / 2).toFloat()  
        val dy = with(textPaint.fontMetricsInt) {  
            (bottom - top) / 2 - bottom  
        }  
        val baseLine = (height / 2 + dy).toFloat()  
        canvas.drawText(centerText, dx, baseLine, textPaint)  
    }  

    // 7.其他 写几个方法动起来  
    @Synchronized  
    fun inflateStepMax(stepMax: Int) {  
        this.stepMax = stepMax  
    }  

    @Synchronized  
    fun inflateCurrentStep(currentStep: Int) {  
        this.currentStep = currentStep  
        //不断绘制onDraw()  
        invalidate()  
    }  
}

3.4 界面调用

val view = findViewById<StepView>(R.id.step_view).apply {  
    inflateStepMax(4000)  
}  
//属性动画,后面的内容  
val anim = with(ObjectAnimator.ofFloat(0f, 3000f)) {  
    duration = 1000  
    start()  
    interpolator = DecelerateInterpolator()  
    addUpdateListener {  
        val currentStep = (it.animatedValue as Float).toInt()  
        view.inflateCurrentStep(currentStep)  
    }  
    this  
}