View的绘制流程

DecorView ViewRoot

DecorView 就是整个Window窗口的最顶层View。

ViewRoot 对应于 ViewRootImpl 类,是连接 WindowManager DecorView 的纽带。
ActivityThread 中当 activity对象被创建好后,会将DecorView加入到Window 中同时完成 ViewRootImpl 的创建并建立和DecorView 的联系。


    DecorView是怎么添加到窗口的呢?这时候我们不得不从Activity是怎么启动的说起。

    当Activity初始化Window,并将布局添加到 PhoneWindow的内部类 DecorView 类之后,ActivityThread 类会调用 handleResumeActivity() 将顶层视图DecorView添加到PhoneWindow窗口。来看看handlerResumeActivity() 的实现:

class ActivityThread{
     final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
            ..................
            //通知Activity组件,让Activity的onResume被调用
            ActivityClientRecord r = performResumeActivity(token, clearHide);

            if (r.window == null && !a.mFinished && willBeVisible) {
                //获得当前Activity的PhoneWindow对象
                r.window = r.activity.getWindow();
                //获得当前phoneWindow内部类DecorView对象
                View decor = r.window.getDecorView();
                //设置窗口顶层视图DecorView可见度
                decor.setVisibility(View.INVISIBLE);
                //得当当前Activity的WindowManagerImpl对象
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    //标记根布局DecorView已经添加到窗口
                    a.mWindowAdded = true;
                    //将根布局DecorView添加到当前Activity的窗口上面
                    wm.addView(decor, l);

            .....................
     }
}

经过一系列方法调用,将 DecorView添加到窗口window,并开始绘制view的过程(如下图):



View绘制是从根节点(Activity中就是DecorView)开始,是一个自上而下的过程。

View的绘制经历三个过程:Measure、Layout、Draw。基本流程如下图

既然如此,我们先从 performTraversals()源码看起   (traversal:遍历)

private void performTraversals() {  
    final View host = mView; //mView就是setView()中传来DecorView对象  
    ...  
    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  //测量
    ...  
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());  //摆放
    ...  
    draw(fullRedrawNeeded);  //绘制
} 
可以看出,绘制过程跟流程图所展示的一模一样。

Mesure过程

Measure的作用就是计算视图大小。

一图概括measure过程:

     


View中的相关方法主要有3个:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)  
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
调用关系:  measure → onMeasure→ setMeasureDimension;

measure,setMeasureDimension是final类型,view的子类不需要重写,只有onMeasure可被重写。

  • measure()

        widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽高的规格和大小(mode & size)。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 
    ......
    onMeasure(widthMeasureSpec, heightMessureSpec);//measure调用自身的onMeasure方法

    //如果在上面的onMeasure中没有用setMeasuredDimension设置视图的宽高,就抛异常
    if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
            throw new IllegalStateException("onMeasure() did not set the"  
                    + " measured dimension by calling"  
                    + " setMeasuredDimension()");  
    }
    ...
}

根据measure方法源码,如果要重写onMeasure,需要要调用setMeasuredDimension(或者super.onMeasure()即View.onMeasure)来设置自身的mMeasuredWidth和mMeasuredHeight,否则,就会抛出异常.

  • View. onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    //设置view的宽高值; getDefaultSize()获取视图大小,这里传入默认最小宽高
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
}

getSuggestedMinimumWidth()怎么获取默认的最小宽高?

    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
返回的是View属性当中的最小值,也就是 android:minWidth android:minHeight 的值,前提是View没有设置背景属性。否则就在最小值和背景的最小值中间取最大值。

setMeasuredDimension() 用来设置view的宽高(即,设置自身的mMeasuredWidth和mMeasuredHeight):

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    mMeasuredWidth = measuredWidth;  
    mMeasuredHeight = measuredHeight;  
  
    mPrivateFlags |= MEASURED_DIMENSION_SET;  
} 

再看一下onMeasure()中用到的 getDefaultSize()

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;//在onMeasure中size传递的是android:minWidth或minHeight
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) { //view的specMode参与决定view的size
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //AT_MOST和EXACTLY时,宽高为父容器测量子view得出的size
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

这里用到了MeasureSpec

  |— 在测量的过程中MeasureSpec类贯穿全程,了解View的测量过程,最合适的切入点就是MeasureSpec。

  |— 自定义view复写 onMeasure()时,常常用到该类的方法:getMode()、getSize()、makeMeasureSpec()

    /** MeasureSpec用1个二进制数同时记录mode和size,减少内存分配而提高性能 */
    public static class MeasureSpec {
        //int长度为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位
        private static final int MODE_SHIFT = 30;
        /**运算遮罩,0x3为16进制,10进制为3;二进制为11,3向左进位30,就是11 0...00(11后跟30个0)
        * 遮罩的作用是用1标注需要的值,0标注不要的值。
        * 因为1与任何数做与运算都得任何数(1&1=1,1&0=0);0与任何数做与运算都得0
        */
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        // 0向左进位30,就是00 0...00(00后跟30个0)
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        // 1向左进位30,就是01 00...0(01后跟30个0)  
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        // 2向左进位30,就是10 00...0(10后跟30个0) 
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //measureSpec=size+mode;得到一个32位的二进制数,最高2位代表mode,其他30位代表size
        //例如 size=100(即4的二进制值),mode=AT_MOST,则measureSpec=100+100...00=100..00100
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }

        // mode = measureSpec & MODE_MASK
        //原理:MODE_MASK = 11 0...0(30个0),1&N=N(N为0或1),0&N=0
        //即,用MODE_MASK后30位的0替换掉measureSpec后30位中的1,只保留32和31位的mode值
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        // size = measureSpec & ~MODE_MASK
        //原理同上,MODE_MASK取反等于 00 11...11(30个1),运算后保留前30位的size值
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

MeasureSpec 的值由 specSizespecMode共同组成的,其中specSize表示大小,specMode表示模式。

MODE_MASK 是长度为30的二进制数,前两位标示Mode,后面的标示Size。

specMode 一共有三种模式:

1. EXACTLY

表示父容器已经检测出子View所需要的精确大小。在该模式下,View的测量大小即为specSize。

2. AT_MOST

表示父容器未能检测出子View所需要的精确大小,但是指定了一个可用大小即specSize 。在该模式下,子View的测量大小不能超过SpecSize。

子view最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。

3. UNSPECIFIED

父容器不对子View的大小做限制.
这种模式一般用作Android系统内部,或者ListView和ScrollView等滑动控件,在此不做讨论。这种情况比较少见,不太会用到。


系统给measure方法传入了 widthMeasureSpec heightMeasureSpec,这两个值又是从哪里得到的呢?

通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。

但是最外层的根视图,它的 widthMeasureSpecheightMeasureSpec 又是从哪里得到的呢?

这就需要去分析ViewRoot中的源码了,观察 performTraversals()方法可以发现如下代码:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

可以看到,这里调用了 getRootMeasureSpec() 去获取widthMeasureSpecheightMeasureSpec 的值,

注意方法中传入的参数,其中 lp.width lp.height 在创建ViewGroup实例的时候就被赋值了,它们都等于 MATCH_PARENT

然后看下 getRootMeasureSpec() 中的代码,如下所示:

private 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;
}

可以看到,这里使用了 MeasureSpec.makeMeasureSpec() 方法来组装一个MeasureSpec

  1. 当 rootDimension = MATCH_PARENT 或 精确值 的时候,MeasureSpec的 specMode就等于EXACTLY
  2. 当 rootDimension = WRAP_CONTENT 的时候,MeasureSpec的 specMode 就等于 AT_MOST

另外,MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。


ViewGroup类没有onMeasure方法,其子类会各自复写View类的onMeasure()。

DecorView是继承自FrameLayout的,那么我们来看看FrameLayout类中的onMeasure方法的实现:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ..............
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        //遍历子view
        for (int i = 0; i < count; i++) { 
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //测量FrameLayout下每个子视图View的宽和高
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
        ......
        //设置当前FrameLayout测量结果,此方法的调用表示当前View测量的结束。
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
}
为什么说父View会影响子View的MeasureSpec的生成?来看上面用到的 ViewGroup.measureChildWithMargins()
    /**
     * parentWidthMeasureSpec和parentHeigthMeasureSpec表示父容器的宽高的MeasureSpec
     * widthUsed和heightUsed表示父容器在水平(竖直)方向已经占用的大小
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // mPaddingLeft和mPaddingRight表示父容器左右两内侧的padding
        // lp.leftMargin和 lp.rightMargin表示子View左右两外侧的margin
        // 这些值相加得到父容器在水平方向已经占用的空间
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        //同理,mPaddingTop + mPaddingBottom +...表示父容器在竖直方向已经占用的空间
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
该方法中调用 getChildMeasureSpec方法来获得ViewGroup下的子视图的测量规格。然后将测量规格作为参数传递给View的measure方法,最终完成所有子视图View的测量。

我们来看getChildMeasureSpec() :

    /**
     * 在measureChildren中最难的部分:找出传递给child的MeasureSpec。
     * 目的是结合父view的MeasureSpec与子view的LayoutParams信息去找到最好的结果
     * (也就是说子view的确切大小由两方面共同决定:1.父view的MeasureSpec 2.子view的LayoutParams属性)
     * 
     * @param spec 父view经过makeMeasureSpec计算的详细测量值(MeasureSpec)
     * @param padding view当前尺寸的的内边距和外边距(padding,margin)
     * @param childDimension 子view在当前尺寸下的布局参数宽高值(LayoutParam.width,height)
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    	//1.从父容器的MeasureSpec获取mode和size
        int specMode = MeasureSpec.getMode(spec);	
        int specSize = MeasureSpec.getSize(spec);	

        //2.得到父容器在水平方向或垂直方向剩余可用的宽高值(父的宽高减去已占用大小)
        int size = Math.max(0, specSize - padding);

        //子view想要的实际大小和模式(需要计算)
        int resultSize = 0;
        int resultMode = 0;

        //3.根据父容器的MeasureSpec和子view的LayoutParams来确定子view的mode和size
        switch (specMode) { //父
        // 当父容器的模式为EXACITY时,父view强加给子view确切的值
        case MeasureSpec.EXACTLY: //MATCH_PARENT或dp值
            // 当子view的LayoutParams>0也就是有确切的值,而不是match_parent或wrap_content
            if (childDimension >= 0) {
             	//子view大小为子自身所赋的值,模式大小为EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            // 当子view的LayoutParams为MATCH_PARENT时(-1)
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
             	//子view大小为父view大小,模式为EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            // 当子view的LayoutParams为WRAP_CONTENT时(-2)    
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
             	//子view决定自己的大小,但最大不能超过父view,模式为AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 当父容器的模式为AT_MOST时,父view强加给子view一个最大的值。
        case MeasureSpec.AT_MOST: //WRAP_CONTENT
        	// 道理同上
            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;

        // 当父容器的模式为UNSPECIFIED时,子view为想要的值
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // 子view大小为子自身所赋的值
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            	// 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

getChildMeasureSpec() 的第一个参数spec和第二个参数padding也可以看出:
父容器的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec

明白了该方法的参数,我们再来看方法的具体实现步骤。
       第一步: 得到父容器的specMode和specSize;
       第二步: 得到父容器在水平方向或垂直方向可用的最大空间值;
       第三步: 确定子View的specMode和specSize。
在第三步出现了一个很关键的switch语句,在此根据父容器的specMode的不同来决定子View的specMode和specSize.

  • 情况1: 父容器的specMode为MeasureSpec.EXACTLY

我们首先看到一个if判断 if (childDimension >= 0) childDimension是之前父容器传来的子View.getLayoutParams拿到的宽高

或许看到这有点懵了:childDimension>=0是啥意思?难道还有小于0的情况?是的,请注意两个系统常量:
     LayoutParams.MATCH_PARENT=-1 和 LayoutParams.WRAP_CONTENT=-2
所以这里表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。
那么:子View的size就是childDimension即我们设置给它的px值,子View的mode也为MeasureSpec.EXACTLY

看完这个if,我们来看第一个else if:  else if (childDimension == LayoutParams.MATCH_PARENT) 
表示当子View的宽或高是LayoutParams.MATCH_PARENT,

子View的size就是父容器在水平方向或垂直方向剩余可用的最大空间值即size(size是第二步得出的值),子View的mode也为MeasureSpec.EXACTLY。

我们来看第二个else if:   else if (childDimension == LayoutParams.WRAP_CONTENT) 
表示子View的宽或高是LayoutParams.WRAP_CONTENT,

子View的size就是父容器在水平方向或垂直方向剩余可用的最大空间值即size,子View的mode为MeasureSpec.AT_MOST。

  • 情况2:父容器的specMode为MeasureSpec.AT_MOST

还是先看这个if判断:  if (childDimension >= 0) 
表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。
那么:子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY。

继续看第一个else if:  else if (childDimension == LayoutParams.MATCH_PARENT) 
表示子View的宽或高是LayoutParams.MATCH_PARENT,
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.AT_MOST。

接着看第二个else if:  else if (childDimension == LayoutParams.WRAP_CONTENT) 
表示子View的宽或高是LayoutParams.WRAP_CONTENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.AT_MOST。

  • 情况3:父容器的specMode为MeasureSpec.UNSPECIFIED

还是先看这个if判断   if (childDimension >= 0) 
表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。
那么:子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY。

继续看第一个else if:  else if (childDimension == LayoutParams.MATCH_PARENT) 
表示子View的宽或高是LayoutParams.MATCH_PARENT。
那么:子View的size为0,子View的mode为MeasureSpec.UNSPECIFIED

接着看第二个else if:  else if (childDimension == LayoutParams.WRAP_CONTENT) 
表示子View的宽或高是LayoutParams.WRAP_CONTENT。
那么:子View的size为0,子View的mode为MeasureSpec.UNSPECIFIED。

至此,我们可以清楚地看到:
子View的MeasureSpec由其父容器的MeasureSpec和该子View本身的布局参数LayoutParams共同决定。
在此经过测量得出的子View的MeasureSpec是系统给出的一个期望值(参考值),我们也可摒弃系统的这个测量流程,直接调用setMeasuredDimension( )设置子View的宽和高的测量值。

对于以上的分析可用表格来规整各一下MeasureSpec的生成:

   

为了便于理解和记忆,再用大白话再对该图进行详细的描述:

1. 当子View采用具体的值(如100px)时,不管父容器的MeasureSpec取何值,系统都会把该子View的specMode设为MeasureSpec.EXACTLY,把specSize设为子View自己指定的大小(childSize)。

2. 当子View的LayoutParams的宽(高)采用match_parent时:系统返回给该子View的specMode就为父容器的specMode,系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)。(暂不考虑unspecified情况

换句话说,子View的大小很明确,就是要和父容器一样大,specMode也一样;更加直白地说就是父容器要怎样子View就要怎样。

3. 当子View的宽(高)为wrap_content是,系统返回给该子View的specMode就为AT_MOST,系统返回给该子View的specSize就为该父容器剩余空间的大小(parentLeftSize)。(暂不考虑unspecified情况

补充说明:

自定义View在重写onMeasure()的过程中要处理View的宽或高为wrap_content的情况(请参见下图中的绿色标记部分)

我们该怎么处理呢?
情况1:
如果在xml布局中View的宽高均为wrap_content.那么需要设置View的宽高为默认宽高,即 mWidth和mHeight.
情况2:
如果在xml布局中View的宽和高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可.

具体的实现可以这样做:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec , heightMeasureSpec);  
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);  
    int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);  
    int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);  
    int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);  
    // view的宽高都为wrap_content时 
    if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){  
        setMeasuredDimension(mWidth, mHeight);
    }else if(widthSpecMode==MeasureSpec.AT_MOST){ //只有宽为wrap_content时 
        setMeasuredDimension(mWidth, heightSpceSize);  
    }else if(heightSpecMode==MeasureSpec.AT_MOST){ //只有高为wrap_content时  
        setMeasuredDimension(widthSpceSize, mHeight);  
    }  
 }

 

至此,整个View树型结构的布局测量流程可以归纳如下:

       

Measure总结

  1. measure方法是final类型的,子类不可以重写;子类可重写onMeasure方法来测量自己的大小:使用setMeasuredDimension或者super.onMeasure()。
  2. View测量结束的标志是调用了View类中的setMeasuredDimension成员方法。
  3. 在Activity生命周期onCreate和onResume中调用 View.getWidth()View.getMeasuredHeight() 返回值都为0,是因为当前View的测量还没有开始,这里关系到Activity启动过程,文章开头说了当ActivityThread类中的performResumeActivity() 会触发Activity执行onResume(), 执行之后才将DecorView添加到PhoneWindow窗口上,开始测量。在Activity生命周期onCreate中performResumeActivity还未执行,因此调用View.getMeasuredHeight() 返回值为0。
  4. 子视图View的大小是由父容器View和子视图View布局共同决定的。

Layout过程

回看performTraversals()源码,调用了layout() :

 private void performTraversals() {
        ..................
        //DecorView请求布局
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());        
        ..................
}
给DecorView传入了屏幕宽高,所以DecorView根布局是充满整个屏幕的。

来看View.layout()源码:

    public void layout(int l, int t, int r, int b) {
        //判断是否需要重新测量
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        //保存上一次View的四个位置,传递给下面的布局变化监听器的onLayoutChange方法
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //设置当前视图View的左,顶,右,底的位置,并且判断布局是否有改变
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //如果布局有改变,条件成立,则视图View重新布局
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //调用onLayout,将具体布局逻辑留给子类实现
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {//监听布局变化
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
代码第13-14行设置当前View的布局位置,也就是当调用了 setFrame(l, t, r, b)方法之后,当前View布局基本完成,

既然这样为什么还要第18行 onLayout方法呢?稍后解答,这里来分析一下setFrame是怎么设置当前View的布局位置的。

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        //当上,下,左,右四个位置有一个和上次的值不一样都会重新布局
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;
            //得到本次和上次的宽和高
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            //判断本次View的宽高和上次View的宽高是否相等
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            //清除上次布局的位置
            invalidate(sizeChanged);
            //保存当前View的最新位置
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

            //如果当前View的尺寸有所变化
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            ...............
        return changed;
    }
1.代码第4行,如果当前View视图的最新位置和上一次不一样时,则View会重新布局。

2.代码第19-25行,保存当前View的最新位置,到此当前View的布局基本结束。从这里我们可以看到,四个全局变量 mLeft,mTop,mRight,mBottom在此刻赋值,联想我们平时使用的View.getWidth()获得View的宽高,你可以发现,其实View. getWidth()方法的实现如下:

public final int getWidth() {
        return mRight - mLeft;
}
public final int getHeight() {
        return mBottom - mTop;
}
以上两个方法是获得View布局时候的宽高,mLeft、mRight等几个变量都是在 layout()中才赋值,

因此,只有在View 布局完之后调用View.getWidth()、getHeight()才能真正获取到准确的宽高值

补充:另一种获取宽高的方法:getMeasuredWidth()、getMeasuredHeight(),顾名思义,要在measure完了之后就可以获取宽高(setMeasuredDimesion里赋值)


上面提到,layout方法调用了setFrame(),也就是当前View的布局结束了,那么View中的onLayout方法又是干嘛的呢?

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
发现onLayout是一个空方法,为什么会这样呢?

因为onLayout()过程是为了确定view在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

既然如此,我们来看下ViewGroup中的onLayout()方法是怎么写的吧,代码如下:

@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到, ViewGroup.onLayout() 是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。

DecorView是继承自FrameLayout的,那么进入FarmeLayout类中看看 onLayout方法的实现吧:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false);
    }

    void layoutChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        mForegroundBoundsChanged = true;
        //遍历当前FrameLayout下的子View,调用子view的layout方法进行布局
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //当子View的Visibility=GONE时,不对它进行布局,所以设置为GONE时,该view是不占据空间的。
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //获得子视图View的宽高
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                //一下代码获得子视图View的四个位置,用于子视图View布局。
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                //子视图布局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
在FrameLayout中的 onLayout()中仅仅是调用了 layoutChildren(),从该方法名称我们不难看出,原来该方法的作用是给子View进行布局的。

第21行,对每个子View的可见度进行了判断,如果visibility=GONE,则当前子View不进行布局,这也就是为什么可见度为GONE时是不占据屏幕空间的,而其他两种VISIBLE和INVISIBLE是占据屏幕空间的。

到此,我们的布局流程可以用如下图表示:

      

Layout总结

  1. 视图View的布局逻辑是由父View,也就是ViewGroup容器布局来实现的。因此,我们如果自定义View一般都无需重写onLayout方法,但是如果自定义一个ViewGroup容器的话,就必须实现onLayout方法,因为该方法在ViewGroup是抽象的,所有ViewGroup的所有子类必须实现onLayout方法。
  2. View在布局中使用 android:visibility=”gone” 属性时,是不占据屏幕空间的。
  3. 必须在View布局完之后调用getHeight()和getWidth()方法才能获取到view的准确宽高。


Draw流程


View.draw()  的源码很长,这里只用一张流程图概括它的处理逻辑:

     

直接看第③步,绘制View视图内容,调用了 onDraw(),来看它的源码:

    // Implement this to do your drawing.
    protected void onDraw(Canvas canvas) {
    }
该方法体里面是一个空实现,也就是视图View将绘制的逻辑留给继承它的子类去实现,

这也就是为什么我们在自定义View的时候必须去实现其父类的onDraw方法来完成自己对内容的绘制。


看第④步,绘制子视图内容,调用了 dispatchDraw(),来看它的源码:

    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {
    }
这也是一个空实现,那么其实现的逻辑也会在它的子类中实现了;

由于只有ViewGroup容器才有其子视图,因此,该方法的实现应该在ViewGroup类中,我们进入ViewGroup类中看其源码如下:

 @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;
        //判断当前ViewGroup容器是否设置的布局动画
        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

            final boolean buildCache = !isHardwareAccelerated();
            //遍历给每个子视图View设置动画效果
            for (int i = 0; i < childrenCount; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    final LayoutParams params = child.getLayoutParams();
                    attachLayoutAnimationParameters(child, params, i, childrenCount);
                    bindLayoutAnimation(child);
                    if (cache) {
                        child.setDrawingCacheEnabled(true);
                        if (buildCache) {
                            child.buildDrawingCache(true);
                        }
                    }
                }
            }
            //获得布局动画的控制器
            final LayoutAnimationController controller = mLayoutAnimationController;
            if (controller.willOverlap()) {
                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
            }
            //开始布局动画
            controller.start();

            mGroupFlags &= ~FLAG_RUN_ANIMATION;
            mGroupFlags &= ~FLAG_ANIMATION_DONE;

            if (cache) {
                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
            }
            //设置布局动画的监听事件
            if (mAnimationListener != null) {
                mAnimationListener.onAnimationStart(controller.getAnimation());
            }
        }

        int clipSaveCount = 0;
        //是否需要剪裁边距
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {
            clipSaveCount = canvas.save();
            //对画布进行边距剪裁
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
        }
        ......
  
        //遍历绘制当前视图的子视图View
        for (int i = 0; i < childrenCount; i++) {
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        if (preorderedList != null) preorderedList.clear();

        ......

        //更新布局动画的监听事件
        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                mLayoutAnimationController.isDone() && !more) {
            // We want to erase the drawing cache and notify the listener after the
            // next frame is drawn because one extra invalidate() is caused by
            // drawChild() after the animation is over
            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
            final Runnable end = new Runnable() {
               public void run() {
                   notifyAnimationListener();
               }
            };
            post(end);
        }
    }
遍历绘制当前容器布局ViewGroup的子视图时,调用了成员方法 drawChild() 来完成子视图的绘制。它的源码如下:
    /**
     * Draw one child of this View Group. This method is responsible for getting
     * the canvas in the right state. This includes clipping, translating so
     * that the child's scrolled origin is at 0, 0, and applying any animation
     * transformations.
     *
     * @param canvas The canvas on which to draw the child
     * @param child Who to draw
     * @param drawingTime The time at which draw is occurring
     * @return True if an invalidate() was issued
     */
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
此处又调用 View.draw() 来绘制视图,因此形成了一个嵌套调用,直到所有的子视图View绘制结束。到此关于视图View绘制已经基本完成。
    

下一篇: View的状态、重绘


参考文章:

1. 从ViewRootImpl类分析View绘制的流程

2. Android View绘制过程

3. 自定义View系列教程02--onMeasure源码详尽分析

4. Android View系统解析(下)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值