View工作原理 -- 基础知识

一、ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,View的三大流程均是通过ViewRoot来完成的。
在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。过程如下:

root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来。
measure过程:决定了View的测量宽/高,measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取View测量后的宽/高,在几乎所有的情况下它都等同于View最终的宽/高,但是特殊情况除外。
layout过程:决定了View的四个顶点的坐标和View的最终宽/高,完成以后,可以通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。
draw过程:决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

二、MeasureSpec

在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽/高。
MeasureSpec代表一个32位int值,高2位代表SpecMode,即测量模式;低30位代表SpecSize,即在某种测量模式下的规格大小。
SpecMode有三类
UNSPECIFIED:父容器不对View有任何限制,要多大给多大。
EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和固定大小这两种模式。
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

三、MeasureSpec和LayoutParams的对应关系

View的LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
对于顶级View(即DecorView),其MeasureSpec由窗口的尺寸和自身的LayoutParams来共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同确定;
MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。

1.DecorView的MeasureSpec

ViewRootImpl#measureHierarchy:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); //desiredWindowWidth为屏幕宽度尺寸
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //desiredWindowHeight为屏幕高度尺寸
perforMeasure(childWidthMeasureSpec , childHeightMeasureSpec );

ViewRootImpl#getRootMeasureSpec:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch(rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;           
    }
    return measureSpec;
}

DecorView的MeasureSpec的创建过程,根据LayoutParams中的宽/高的参数来划分:
1.LayoutParams.MATCH_PARENT:精确模式;大小为窗口的大小。
2.ViewGroup.LayoutParams.WRAP_CONTENT:最大模式;大不不定,但是不能超过窗口的大小。
3.固定大小:精确模式;大小为LayoutParams中指定的大小。

2.普通View的MeasureSpec

ViewGroup#measureChildWithMargins:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUserd, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUserd, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec );
}

ViewGroup#getChildMeasureSpec:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec); //父容器的SpecMode
    int specSize = MeasureSpec.getSize(spec); //父容器的SpecSize
    int size = Math.max(0, specSize - padding); //注意:size表示父容器的剩余空间大小,等于父容器的SpecSize减去父容器中已占用的空间大小
    int resultSize = 0;  //当前View的SpecSize
    int resultMode = 0; //当前View的SpecMode

    switch(specMode) { //父容器的SpecMode
    case MeasureSpec.EXACTLY: //父容器的SpecMode为精确模式
        if(childDimension >= 0) { //当前View的LayoutParams为固定大小
            resultSize = childDimension; //当前View的SpecSize为固定大小
            resultMode = MeasureSpec.EXACTLY; //当前View的SpecMode为精确模式
        } else if(childDimension == LayoutParams.MATCH_PARENT) { 
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if(childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    case MeasureSpec.AT_MOST:
         if(childDimension >= 0) { 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if(childDimension == LayoutParams.MATCH_PARENT) { 
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if(childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
     case MeasureSpec.UNSPECIFIED:
         if(childDimension >= 0) { 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if(childDimension == LayoutParams.MATCH_PARENT) { 
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if(childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

普通View的MeasureSpec的创建过程,根据父容器的MeasureSpec、子元素的LayoutParams、父容器的剩余空间大小来确定。
其中,父容器的剩余空间大小等于父容器的SpecSize减去父容器中已占用的空间大小,而
父容器中已占用的空间大小由父容器的padding、子元素的margin以及已使用的空间大小三部分组成。

规则如下:
注意:parentSize是指父容器的剩余空间大小

  1. 父容器的SpecMode为EXACTLY
    1.1 子元素的LayoutParams为固定大小
    子元素的SpecMode为EXACTLY,SpecSize为固定大小。
    1.2 子元素的LayoutParams为MATCH_PARENT
    子元素的SpecMode为EXACTLY,SpecSize为parentSize。
    1.3 子元素的LayoutParams为WRAP_CONTENT
    子元素的SpecMode为AT_MOST,SpecSize为parentSize。
  2. 父容器的SpecMode为AT_MOST
    2.1 子元素的LayoutParams为固定大小
    子元素的SpecMode为EXACTLY,SpecSize为固定大小。
    2.2 子元素的LayoutParams为MATCH_PARENT
    子元素的SpecMode为AT_MOST,SpecSize为parentSize。
    2.3 子元素的LayoutParams为WRAP_CONTENT
    子元素的SpecMode为AT_MOST,SpecSize为parentSize。
  3. 父容器的SpecMode为UNSPECIFIED
    3.1 子元素的LayoutParams为固定大小
    子元素的SpecMode为EXACTLY,SpecSize为固定大小。
    3.2 子元素的LayoutParams为MATCH_PARENT
    子元素的SpecMode为UNSPECIFIED,SpecSize为0。
    3.3 子元素的LayoutParams为WRAP_CONTENT
    子元素的SpecMode为UNSPECIFIED,SpecSize为0。

你可能感兴趣的:(View工作原理 -- 基础知识)