2018.03.15、View 绘制流程学习 笔记

190 阅读5分钟

一、View的测量过程

1、MeasureSpace :封装了父View传递到子View的布局要求。是由size(大小) 和modle(模式组成),有三种模式:

  • UNSPECIFIED:

父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;

  • EXACTLY:

父视图希望子视图的大小是specSize中指定的大小;

  • AT_MOST:

子视图的大小最多是specSize中的大小。

2、measure(int widthMeasureSpec, int heightMeasureSpec):当父View对子view进行测量时,会调用的方法。两个入参分别代表对子View的限制。

  • 1)先判断是否需要进行测量: View.measure()方法时View并不是立即就去测量,而是先判断一下是否有必要进行测量操作,如果不是强制测量或者MeasureSpec与上次的MeasureSpec相同的时候,那么View就不需要重新测量了.

  • 2)从缓存中读取是否测量过: 如果不满足上面条件,View就考虑去做测量工作了.但在测量之前,View还想偷懒,如果能在缓存中找到上次的测量结果,那直接从缓存中获取就可以了。它会以MeasureSpec计算出的key值作为键,去成员变量mMeasureCache中查找是否缓存过对应key的测量结果,

  • 3)onMeasure()方法进行测量: 如果不能从mMeasureCache中读到缓存过的测量结果,只能乖乖地调用onMeasure()方法去完成实际的测量工作,并且将尺寸限制条件widthMeasureSpec和heightMeasureSpec传递给onMeasure()方法。

  • 4)保存测量结果: 最终View都会得到测量的结果,并且将结果保存到mMeasuredWidth和mMeasuredHeight这两个成员变量中,同时缓存到成员变量mMeasureCache中,以便下次执行measure()方法时能够从其中读取缓存值

3、onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

4、setMeasuredDimension()

  • 1)通过setMeasuredDimensionRaw() ,把测量完的宽高值赋值给mMeasuredWidth、mMeasuredHeight

二、

1、ViewGroup怎么知道他的子View是多大呢?View提供了以下三组方法:

  • getMeasuredWidth()和getMeasuredHeight()
  • getMeasuredWidthAndState()和getMeasuredHeightAndState()
  • getMeasuredState()

2、mMeasuredWidth是一个Int类型的值,其是由4个字节组成的。

  • 其高位的第一个字节为第一部分,用于标记测量完的尺寸是不是达到了View想要的宽度,我们称该信息为测量的state信息。
  • 其低位的三个字节为第二部分,用于存储测量到的宽度。
  • 有点类似于measureSpec

3、resolveSizeAndState()

  • 1)这个方法的代码结构跟前文提到的getDefaultSize()方法很相似。

三、ViewGroup的measure过程

1、对于ViewGroup来说,除了完成自己的measure过程,还会遍历去调用所有子元素的measure()方法,各个子元素再递归去执行这个过程

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            //遍历每个子元素,如果该子元素不是GONE的话,就去测量该子元素
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

2、对每个子view 再次进行测量

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        //获取child自身的LayoutParams属性
        final LayoutParams lp = child.getLayoutParams();
        //根据父布局的MeasureSpec,父布局的padding和child的LayoutParams这三个参数,通过getChildMeasureSpec()方法计算出子元素的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        //调用measure()方法测量child,前文已经解释过这个方法,
        //调用该方法之后会将view的宽高值保存在mMeasuredWidth和mMeasuredHeight这两个属性当中,这样child的尺寸就已经测量出来了
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChild()的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec()方法来创建子元素的MeasureSpec,接着将MeasureSpec传给View的measure()方法来完成对子元素的测量。

3、getChildMeasureSpec(int spec, int padding, int childDimension)

  • getChildMeasureSpec()这个方法清楚展示了普通View的MeasureSpec的创建规则,每个View的MeasureSpec状态量由其直接父View的MeasureSpec和View自身的属性LayoutParams(LayoutParams有宽高尺寸值等信息)共同决定。

4、MeasureSpec状态后,将其与尺寸值通过makeMeasureSpec(int size,int mode)方法结合在一起,就是最终传给View的onMeasure(int, int)方法的MeasureSpec值了。

5、ViewRootImpl是连接WindowManager和DecorView的纽带,控件的测量、布局、绘制以及输入事件的分发处理都由ViewRootImpl触发。
performTraversals()它调用了一个performTraversals()方法使得View树开始三大工作流程

private void performTraversals() {
            ...
        if (!mStopped || mReportNextDraw) {
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    
            ...
            }
        } 

        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
        }


        if (!cancelDraw && !newSurface) {
            performDraw();
        }
        ...
}

6、performTraversals() 通过调用performMeasure()、performLayout()、performDraw()这三个方法,这三个方法分别完成DecorView的measure、layout、和draw这三大流程,其中performMeasure()中会调用measure()方法,在measure()方法中又会调用onMeasure()方法,在onMeasure()方法中会对所有子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。

7、树是遍历顺序,这意味着父View将被先绘制,子View视图后被绘制。

8、在measure过程中,通过 widthMeasureSpec() heightMeasureSpec 以及View自身的LayoutParams共同决定子视图的测量规格;

  • 1)MeasureSpec :父View对子View的测量模式、测量大小的要求。

  • 2)子View 视图通过LayoutParams,这个类并告诉父View 视图他们应该怎样被测量和放置。

  • 3)它的尺寸可以有三种表示方法:

    1、具体数值 2、FILL_PARENT 3、WRAP_CONTENT

9、对于不同的ViewGroup的子类,有着各自不同的LayoutParams。

10、getMeasuredWidth()、getMeasuredHeight()返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值。

public final int getMeasuredWidth() {  
        return mMeasuredWidth & MEASURED_SIZE_MASK;  
    }  
public final int getWidth() {  
        return mRight - mLeft;  
    }  

11、draw方法步骤:绘制背景---------绘制自己--------绘制chrildren----绘制装饰。