美文网首页
UI绘制流程,源码阅读3

UI绘制流程,源码阅读3

作者: 揾食艰难 | 来源:发表于2019-10-09 21:29 被阅读0次

上一节,我们知道view绘制流程,下面我们针对每一个步骤分析:

graph LR
performMeasure --> performLayout
performLayout --> performDraw

==performMeasure== 测量view尺寸规格

performMeasure方法进去,实际上看到的是调用mView.measure,而mView实际上是我们的顶层decorView

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

mView.measure进去实际是一个final方法,是不可以被子类重写的,这个方法首先会从缓存里面找,找不到的情况下会继续调用==onMeasure==方法。

graph LR
onMeasure-->setMeasuredDimension
setMeasuredDimension-->setMeasuredDimensionRaw

上面这几个方法调用后的目的只有一个,对==mMeasuredWidth== 和 ==mMeasuredHeight==进行赋值操作


接下里要了解view的测量过程,那么我们首先需要了解一个类:==MeasureSpec==

view的测量,需要测量模式+尺寸两部分,而MeasureSpec是对view的测量规格的一个封装。
MeasureSpec里面对应一个32位的int值,例如:00000000000000000000000000000000
前2位数字代表的是模式,后面30位代表的是尺寸

SpecMode(前2位,模式)   +  SpecSize(后30,尺寸)

MeasureSpec的源码里面定义了三种模式:

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    0左移30位,二进制的值:00000000000000000000000000000000
    父容器不对View做任何限制,系统内部使用

       
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    1左移30位,二进制的值:01000000000000000000000000000000
    父容器检测出View的大小,Vew的大小就是SpecSize, LayoutPamras是 match_parent or 固定大小

    
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    2左移30位,二进制的值:10000000000000000000000000000000
    父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras是 wrap_content


我们了解完MeasureSpec后,,看下performMeasure的参数,参数是由==getRootMeasureSpec==方法测量出来的,getRootMeasureSpec这个方法参数的含义是:==windowSize对应的是窗口的大小尺寸,rootDimension是自身(decorView)的LayoutParams属性==

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

上面可以看出,测量出decorview的尺寸后,传进了performMeasure方法,然后继续传入到了==mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)== 方法,mView是decorView,是继承于FrameLayout的,所以实际上调用到了FrameLayout 的 measure方法。

通过看FrameLayout的源码可以知道,FrameLayout调用==measureChildWithMargins==方法测量出子view的尺寸规格,然后调用子 view的 measure 方法,这样子形成了一个递归测量效果,需要注意的是自定义view的时候若不重写onMeasure方法,不管自定义的view是match_parent,wrap_content,最后的大小尺寸都是父容器的尺寸大小

在测量出所有的子控件尺寸后,经过一系列的计算,调用==setMeasuredDimensionRaw== 方法保存自身的宽高尺寸信息

总结:

ViewGroup  measure --> onMeasure(测量子控件的宽高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)

View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)

==performLayout== 决定view位置

performLayout(lp, mWidth, mHeight);

lp:    顶层布局的layoutParams
mWidth:顶层布局的宽
mHeight:顶层布局的高

在源码里面可以看到,host是顶层布局decorview,通过调用==layout==方法来确定自身的位置,即mLeft,mTop,mRight,mBottom,这四个位置是通过调用==setFrame==方法来实现赋值

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

如果是viewGroup,则通过调用==onLayout==方法确定子view的位置

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    
}

总结:


ViewGroup layout(来确定自己的位置,4个点的位置) -->onLayout(进行子View的布局)
View layout(来确定自己的位置,4个点的位置)

==performDraw== 绘制view

graph LR
performDraw-->draw
draw-->drawSoftware
drawSoftware-->mView.draw

从上面可以看到,view的绘制时,调用到了mView.draw方法,此时的mView是DecorView。

从draw方法的源码注释里面可以大概看到绘制view的过程和步骤:

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

总结:

ViewGroup 
绘制背景 drawBackground(canvas)

绘制自己onDraw(canvas)

绘制子View dispatchDraw(canvas)

绘制前景,滚动条等装饰onDrawForeground(canvas)

View 

绘制背景 drawBackground(canvas)

绘制自己onDraw(canvas)

绘制前景,滚动条等装饰onDrawForeground(canvas) 

相关文章

网友评论

      本文标题:UI绘制流程,源码阅读3

      本文链接:https://www.haomeiwen.com/subject/rgrnpctx.html