Android视图层次结构简介

在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系。

AndroidStudio 画流程图 安卓 画流程图_ide


我们平时在Activity中setContentView()中设置的layout,对应的是上图中的ViewGrop。

从Activity启动开始的视图绘制调用过程

ActivityThread.java类是Android应用的入口类,在启动Activity的过程中,会调用ActivityThread的handleResumeActivity()方法,关于视图的绘制过程最初就是从这个方法开始的。

1.从Activity启动到视图绘制的UML时序图

AndroidStudio 画流程图 安卓 画流程图_AndroidStudio 画流程图_02

2.相关关键的类简介

相关类的信息可查看Android窗口机制

3.上述过程源码分析

ActivityThread.handleResumeActivity()方法 :

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ...//其他代码
          if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }
    ...//其他代码
 }

调用WindowManagerImpl.addView() :

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

调用WindowManagerGlobal.addView():

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...//其他代码
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

调用ViewRootImpl.setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
            //将顶层视图DecorView赋值给全局的mView
                mView = view;

            ..//其他代码

            //标记已添加DecorView
             mAdded = true;
            .............
            //请求布局
            requestLayout();

            ..//其他代码
        }
 }

调用了requestLayout()方法:

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

调用了scheduleTraversals():

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这个方法关键的就是调用mChoreographer.postCallback()向mChoreographer提交了一个mTraversalRunnable,等待Choreographer执行这个TraversalRunnable(Choreographer的机制可以查看Android 编舞者Choreographer),看下TraversalRunnable :

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

TraversalRunnable里面就执行了doTraversal()方法:

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

里面执行了关键的performTraversals()方法:

private void performTraversals() {
        // cache mView since it is used so much below...

        final View host = mView;        //mView就是DecorView根布局
        //是否正在遍历
        mIsInTraversal = true;
        //是否马上绘制View
        mWillDrawSoon = true;

        ..//其他代码

        //希望的窗口的宽高
        int desiredWindowWidth;
        int desiredWindowHeight;
        //Window的参数
        WindowManager.LayoutParams lp = mWindowAttributes;
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
              } else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
              }
          }
        ..//其他代码

        ..//其他代码
        //获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
         int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
         int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

        // Ask host how big it wants to be       
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //执行测量操作

        ..//其他代码     
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);   //执行布局操作

        ..//其他代码       
        performDraw(); //执行绘制操作
    }

终于到了performMeasure(),performLayout(),performDraw()这三个关键的方法。
上述代码就是一个完整的绘制流程,包括三个步骤:

1)performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;

2)performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;

3)performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。

接下来就对这三个过程展开分析。

measure过程分析

measure过程关键类

MeasureSpec

测量规格或者测量参数,MeasureSpec是View的静态内部类,封装的是父容器传递给子容器的布局要求。 MeasureSpec中的值是一个整型(32位),其中高两位是mode,后面30位存的是size,是为了减少对象的分配开支。其中mode有三种模式:

  • UNSPECIFIED
    The parent has not imposed any constraint on the child. It can be whatever size it wants.
    父容器对于子容器没有任何限制,子容器想要多大就多大
  • EXACTLY
    The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
    父容器已经为子容器设置了精确的尺寸,无论子容器想要多大的空间,都应当遵循这些边界。
  • AT_MOST
    The child can be as large as it wants up to the specified size.
    子容器可以按其需要大到指定大小。即给出限定的最大值,子View最大只能是这个值。

如果从代码上来看,view.measure(int widthMeasureSpec, int heightMeasureSpec) 的两个MeasureSpec是父View传递过来的,但子View并不是只按照MeasureSpec,而是由·MeasureSpec和子View自己的LayoutParams共同决定的,子View的LayoutParams就是我们在xml写的时候设置的layout_width和layout_height 转化而来的。

即父容器的MeasureSpec和子View的LayoutParams共同决定了子View的MeasureSpec。
在measure阶段,View的宽和高由其measureSpec中的specSize决定。

普通View的MeasureSpec的创建规则:

AndroidStudio 画流程图 安卓 画流程图_AndroidStudio 画流程图_03

ViewGroup.LayoutParams

LayoutParams被view用于告诉它们的父布局它们想要怎样被布局。
该LayoutParams基类仅仅描述了view希望宽和高有多大。对于每一个宽或者高,可以指定为以下三种值中的一个:MATCH_PARENT,WRAP_CONTENT,an exact number。
对ViewGroup不同的子类,也有相应的LayoutParams子类。

measure过程源码分析

ViewRootImpl.performMeasure()方法:

//=============ViewRootImpl.java==============
  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
         ......
         mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
         ......
  }

这个mView就是ViewRootImpl的setView方法()传递进来的DecorView,看下View.measure()方法:

//===========================View.java===============================
    /**
     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      ......
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
      ......
    }

实际测量工作是在onMeasure(int,int)方法中实现的,ViewGroup的实现类必须重写onMeasure()方法,才能绘制该容器内的子View。

View的onMeasure()方法
/**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see android.view.View.MeasureSpec#getMode(int)
     * @see android.view.View.MeasureSpec#getSize(int)
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

里面就一句代码,调用setMeasuredDimension()方法存储测量出的宽度和高度。看一下onMeasure()方法的注释:

/**
测量该view以及它的内容来决定测量的宽度和高度。该方法被measure()方法调用,并且应该被子类重写来对它们的内容进行准确而且有效的测量。

当重写该方法时,您必须调用setMeasuredDimension(int,int)来存储该view测量出的宽和高。如果不这样做将会触发一个IllegalStateException异常,由measure()方法抛出。调用基类的onMeasure(int,int)方法是一个有效的方法。

测量过程在基类中的实现是默认为背景的尺寸,除非更大的尺寸被MeasureSpec所允许。子类应该重写onMeasure(int,int)方法来提供对内容更好的测量。

如果该方法被重写,子类负责确保测量出的高和宽至少是该view的mininum高度值和mininum宽度值(getSuggestedMininumHeight()和getSuggestedMininumWidth());

*/

也就是说,容器类控件(ViewGroup的子类)如FrameLayout、LinearLayout、RelativeLayout等,都会重写onMeasure方法,根据自己的特性来进行测量,最后会调用setMeasuredDimension()方法来存储测量后的宽度和高度;

ViewGroup的measureChildWithMargins()

父View的measure的过程会先测量子View,等子View测量结果出来后再来测量自己,measureChildWithMargins()方法就是用来测量子View的:

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

	// 子View的LayoutParams,你在xml的layout_width和layout_height,
	// layout_xxx的值最后都会封装到这个个LayoutParams。
	final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   
	
	//根据父View的测量规格和父View自己的Padding,
	//还有子View的Margin和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。
	final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            
	mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);    
	
	final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
	mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  
	
	//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
	// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
	child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// spec参数   表示父View的MeasureSpec 
// padding参数    父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
//               子View的MeasureSpec的size
// childDimension参数  表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
//                    可以是wrap_content、match_parent、一个精确指(an exactly size),  
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  

   //父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小。
    int size = Math.max(0, specSize - padding);   
  
    int resultSize = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
  
    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1、父View是EXACTLY的 !  
    case MeasureSpec.EXACTLY:   
        //1.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size为精确值  
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
        }   
        //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT   
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size为父视图大小  
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
        }   
        //1.3、子View的width或height为 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  
        }  
        break;  
  
    // Parent has imposed a maximum size on us  
    //2、父View是AT_MOST的 !      
    case MeasureSpec.AT_MOST:  
        //2.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size为精确值  
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  
        }  
        //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size, but our size is not fixed.  
            // Constrain child to not be bigger than us.  
            resultSize = size;                  //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
        }  
        //2.3、子View的width或height为 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
        }  
        break;  
  
    // Parent asked to see how big we want to be  
    //3、父View是UNSPECIFIED的 !  
    case MeasureSpec.UNSPECIFIED:  
        //3.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size为精确值  
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  
        }  
        //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size... find out how big it should  
            // be  
            resultSize = 0;                        //size为0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
        }   
        //3.3、子View的width或height为 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size.... find out how  
            // big it should be  
            resultSize = 0;                        //size为0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
        }  
        break;  
    }  
    //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}

getChildMeasureSpec()方法非常关键,它根据View自己的MeasureSpec和子View的LayoutParams计算出要传递给子View的MeasureSpec。
上述代码为什么会有这么复杂的判断逻辑:
如果我们在xml 的layout_width或者layout_height 把值都写死,那么上述的测量完全就不需要了,之所以要上面的这步测量,是因为 match_parent 就是充满父容器,wrap_content 就是自己的内容多大自己就多大,我们写代码的时候特别爽,我们编码方便的时候,google就要帮我们计算你match_parent的时候是多大,wrap_content的时候是多大,这个计算过程,就是计算出来的父View的MeasureSpec不断往子View传递,结合子View的LayoutParams一起再算出子View的MeasureSpec,然后继续传给子View,不断计算每个View的MeasureSpec,子View有了MeasureSpec才能测量自己和自己的子View。

上述代码这么理解就比较简单了:

  • 如果父View的MeasureSpec是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。

1)、如果子View 的layout_xxxx是MATCH_PARENT(充满整个父View),父View的大小是确切的,那么子View的大小肯定是确切的,而且大小值就是父View的size。所以这种情况下子View的MeasureSpec的mode=EXACTLY,size=父View的size。

2)、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content 来决定的,但是子View的毕竟是子View,大小不能超过父View的大小,但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子View自己content的大小(比如TextView wrap_content 的时候你要测量TextView content 的大小,也就是字符占用的大小,这个测量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候才能测出字符的大小,假设你字符100px,但是MeasureSpec要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View的MeasureSpec mode的应该是AT_MOST,而size 暂定父View的 size,表示的意思就是子View的大小没有不确切的值,子View的大小最大为父View的大小,不能超过父View的大小(这就是AT_MOST的意思),然后这个MeasureSpec做为子View measure()方法的参数,做为子View的大小的约束,子View再实现自己的测量。所以这种情况下子View的MeasureSpec的mode = AT_MOST,size=父View的size。

3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),那么就更简单了,不管你父View的mode和size是什么,子View写死了就是200dp,那么控件最后展示的就是200dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。

  • 如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定的,只知道最大的大小是MeasureSpec的size值,不能超过这个值。

1)、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能是MeasureSpec的size值),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,因为父View自己都确定不了自己的大小。所以这种情况下子View的mode=AT_MOST,size=父View的size。

2)、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能是MeasureSpec的size值),子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小。所以这种情况下子View MeasureSpec的mode=AT_MOST,size=父View的 size。

3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),同上,写多少就是多少。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。

  • 如果父View的MeasureSpec是UNSPECIFIED(未指定),表示没有任何约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束。

1)、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0。所以这种情况下子View的MeasureSpec的mode = UNSPECIFIED,size=0。

2)、同 1)。 所以这种情况下子View的MeasureSpec的mode = UNSPECIFIED,size=0。

3)、如果如果子View 的layout_xxxx是确定的值(比如200dp),同上,写多少就是多少。(只要设置确切的值,那么无论怎么测量,大小都是不变的,都是layout_xxxx写的那个值)。所以这种情况下子View的MeasureSpec 的mode = EXACTLY, size=子View在layout_xxxx 填的那个值。

ViewGroup的onMeasure()方法

ViewGroup这个抽象类本身并没有实现onMeasure()方法,onMeasure()方法是由ViewGroup的实现类(如FrameLayout、LinearLayout、RelativeLayou)来实现的,来看下FrameLayout的onMeasure()方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  

	...//其他代码

	//步骤1:先测量子View
	
	int maxHeight = 0;
	int maxWidth = 0;
	int childState = 0;
	for (int i = 0; i < count; i++) {    
	   final View child = getChildAt(i);    
	   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
	    // 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法在上面
	    // 的源码已经讲过了,基本思想就是父View把自己的MeasureSpec传给子View,结合子View自己的LayoutParams算出子View的MeasureSpec,然后继续往下传,
	    // 最会传递到叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
	     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);  
	     
	     ...//其他代码
	     	
	   }
	}

	...//其他代码
	
	//步骤2:再测量自己
	
	//所有的子View测量之后,经过一系列的计算之后通过setMeasuredDimension()设置自己的宽和高,
	//对于FrameLayout,根据最大的子View的大小,对于LinearLayout,根据宽度或者高度的累加,具体测量的原理去看看源码。
	//总的来说,父View是等所有的子View测量结束之后,再来测量自己的宽和高,最后调用setMeasuredDimension()方法存储测量出的宽和高。
	setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
	
	...//其他代码
		
}

一个具体例子分析measure过程

到目前为止,基本把measure主要原理都过了一遍,接下来我们结合实例来讲解整个measure的过程,定义一个 R.layout.activity_main 的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"    
   android:id="@+id/linear"
   android:background="@android:color/holo_blue_dark"    
   android:layout_width="match_parent"    
   android:layout_height="wrap_content"    
   android:layout_marginTop="50dp"
   android:paddingBottom="70dp"    
   android:orientation="vertical">    
   <TextView        
    android:id="@+id/text"       
    android:background="@color/material_blue_grey_800"       
    android:layout_width="match_parent"     
    android:layout_height="wrap_content"  
    android:text="TextView"        
    android:textColor="@android:color/white"        
    android:textSize="20sp" />    
   <View       
     android:id="@+id/view"       
     android:background="@android:color/holo_green_dark"
     android:layout_width="match_parent" 
     android:layout_height="150dp"/>
</LinearLayout>

上面的代码对于出来的布局是下面的一张图:

AndroidStudio 画流程图 安卓 画流程图_Android_04


上面的图做些说明:

整个图是一个DecorView,DecorView可以理解成整个页面的根View,DecorView是一个FrameLayout,包含两个子View,一个id=statusBarBackground的View和一个是LineaLayout,id=statusBarBackground的View先不管,而这个LinearLayout比较重要,它包含一个title和一个content,title很好理解其实就是TitleBar或者ActionBar,content是一个FrameLayout,id是android.R.id.content,写的页面布局通过setContentView()加进来就成了content的直接子View。

整个View的层级图如下:

AndroidStudio 画流程图 安卓 画流程图_AndroidStudio 画流程图_05

注: 1、header的是个ViewStub,用来惰性加载ActionBar,为了便于分析整个测量过程,我把Theme设成NoActionBar,这样分析时就可以忽略ActionBar的measure过程。
2、包含Header(ActionBar)和id/content的那个父View,我们就把他叫做ViewRoot,它是垂直的LinearLayout,放着整个页面除statusBar的之外所有的东西。

我们来看下ViewRootImpl的performTraversals()方法

private void performTraversals() {
	
	   ...//其他代码

       int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
       int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

       if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
               + mWidth + " measuredWidth=" + host.getMeasuredWidth()
               + " mHeight=" + mHeight
               + " measuredHeight=" + host.getMeasuredHeight()
               + " coveredInsetsChanged=" + contentInsetsChanged);

        // Ask host how big it wants to be
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

		...//其他代码


}

先调用了getRootMeasureSpec()计算出MeasureSpec

/**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    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;
    }

然后调用performMeasure()对mView进行测量

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其实就是DecorView,整个View的绘制从DecorView开始的。

getRootMeasureSpec(mWidth, lp.width) 和 getRootMeasureSpec(mHeight, lp.height) 的参数mWidth和mHeight是屏幕的宽度和高度,lp是WindowManager.LayoutParams,lp.width和lp.height的默认值是MATCH_PARENT,所以通过getRootMeasureSpec()生成的测量规格MeasureSpec的mode是EXACTLY ,size是屏幕的宽和高。

通过getRootMeasureSpec()生成的两个MeasureSpec传递给了DecorView的measure()方法,DecorView的测量就开始了,我们画出传递给DecorView的MeasureSpec图:

AndroidStudio 画流程图 安卓 画流程图_宽高_06

1、-1 代表的是EXACTLY,-2 是AT_MOST
2、由于屏幕的像素是1440x2560,所以DecorView的MeasureSpec的size对应这两个值

DecorView是一个FrameLayout,接下来在FrameLayout 的onMeasure()方法中分析DecorView的测量子View的过程,DecorView测量完所有的子View再来测量自己。

//FrameLayout 的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
	
	...//其他代码
	
	int maxHeight = 0;
	int maxWidth = 0;
	int childState = 0;
	for (int i = 0; i < count; i++) {    
	   final View child = getChildAt(i);    
	   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
	     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);      
	    
	    ...//其他代码
	    
	   }
	}
	
	...//其他代码
	
	}

先测量的是ViewRoot的大小,调用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
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

ViewRoot 是系统的View,它的LayoutParams默认都是match_parent,根据MeasureSpec的计算规则,计算出ViewRoot的MeasureSpec的mode=EXACTLY,size=DecorView的size。(DecorView的MeasureSpec的mode是EXACTLY,ViewRoot的layoutparams是match_parent)

所以ViewRoot的MeasureSpec图如下:

AndroidStudio 画流程图 安卓 画流程图_宽高_07


算出ViewRoot的MeasureSpec 之后,开始调用ViewRoot.measure 方法去测量ViewRoot的大小,然而ViewRoot是一个LinearLayout ,ViewRoot.measure最终会执行的LinearLayout 的onMeasure 方法,LinearLayout 的onMeasure()方法又开始逐个测量它的子View,上面的measureChildWithMargins()方法又会被调用,那么根据View的层级图,接下来先测量的是header(ViewStub),由于header的Gone,所以直接跳过不做测量工作,所以接下来测量ViewRoot的第二个child content(android.R.id.content),我们要算出content的MeasureSpec,所以又要拿ViewRoot 的MeasureSpec 和 android.R.id.content的LayoutParams 做计算了,计算过程就是调用getChildMeasureSpec()的方法,

AndroidStudio 画流程图 安卓 画流程图_ide_08

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 
   .....//其他代码
   final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  
   .....//其他代码
}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  
  
    //父View的大小-自己的Padding+子View的Margin,得到值才是子View可能的最大值。  
    int size = Math.max(0, specSize - padding); 
     .....//其他代码
}

由上面的代码

int size = Math.max(0, specSize - padding);

其中padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed

算出android.R.id.content 的MeasureSpec 的size。

由于ViewRoot 的mPaddingTop=100px(这个可能和状态栏的高度有关,我们测量的最后会发现id/statusBarBackground的View的高度刚好等于100px,ViewRoot 是系统的View,它的Padding我们没法改变),所以计算出来content 的MeasureSpec 的高度少了100px ,它的宽高的mode也是EXACTLY(ViewRoot 是EXACTLY,android.R.id.content 是match_parent)。所以content 的MeasureSpec 如下(高度少了100px):

AndroidStudio 画流程图 安卓 画流程图_宽高_09


content是FrameLayout,递归调用开始准备计算id/linear的MeasureSpec,我们先给出结果:

AndroidStudio 画流程图 安卓 画流程图_ide_10

图中有两个要注意的地方:
1、id/linear的heightMeasureSpec 的mode=AT_MOST,因为id/linear 的LayoutParams 的layout_height=“wrap_content”
2、id/linear的heightMeasureSpec 的size 少了200px, 由上面的代码
padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
int size = Math.max(0, specSize - padding);
由于id/linear 的 android:layout_marginTop=“50dp” 使得lp.topMargin=200px (本设备的density=4,px=4*pd),在计算后id/linear的heightMeasureSpec的size 少了200px。(布局代码前面已给出,可自行查看id/linear控件xml中设置的属性)

linear.measure()接着往下算linear的子View的的MeasureSpec,看下View层级图,往下走应该是id/text,接下来计算id/text的MeasureSpec,直接看图,mode=AT_MOST ,size 少了280px(父布局linear设置了paddingBottom=“70dp”,根据specSize - padding,所以少了280px),

AndroidStudio 画流程图 安卓 画流程图_ide_11


算出id/text 的MeasureSpec后,接下来执行text.measure(childWidthMeasureSpec, childHeightMeasureSpec);准备测量id/text 的高宽,这时候已经到底了,id/text是TextView,已经没有子View了,这时候就执行TextView的onMeasure()方法了。

TextView 拿着刚才计算出来的heightMeasureSpec(mode=AT_MOST,size=1980),这个就是对TextView的高度和宽度的约束,进到TextView 的onMeasure(widthMeasureSpec,heightMeasureSpec) 方法,在onMeasure 方法执行调试过程中,我们发现下面的代码:

...//其他代码

            int desired = getDesiredHeight();  //desired=107px

            height = desired;
            mDesiredHeightAtMeasure = desired;

            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(desired, heightSize);  //heightSize=1980px
            }
            
            ...//其他代码

TextView中字符的高度(也就是TextView的content高度[wrap_content])测出来=107px,107px 并没有超过1980px(允许的最大高度),所以实际测量出来TextView的高度是107px。
最终算出id/text 的mMeasureWidth=1440px,mMeasureHeight=107px。

TextView的高度已经测量出来了,接下来测量id/linear的第二个child(id/view),同样的原理测出id/view的MeasureSpec.

AndroidStudio 画流程图 安卓 画流程图_AndroidStudio 画流程图_12


id/view的MeasureSpec 计算出来后,调用view.measure(childWidthMeasureSpec, childHeightMeasureSpec)的测量id/view的高宽,之前已经说过View measure的默认实现是:

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

最终算出id/view的mMeasureWidth=1440px,mMeasureHeight=600px。

id/linear 的子View的高度都计算完毕了,接下来id/linear就通过所有子View的测量结果计算自己的高宽,id/linear是LinearLayout,所以它的高度计算简单理解就是子View的高度的累积+自己的Padding.

AndroidStudio 画流程图 安卓 画流程图_ide_13


最终算出id/linear的mMeasureWidth=1440px,mMeasureHeight=987px。

算出id/linear出来后,id/content 就要根据它唯一的子View id/linear 的测量结果和自己的MeasureSpec一起来测量自己的大小,具体计算的逻辑去看FrameLayout的onMeasure()方法的计算过程。以此类推,接下来测量ViewRoot,然后再测量id/statusBarBackground,最后测量DecorView 的高宽,最终整个测量过程结束。所有的View的大小测量完毕,所有View的getMeasuredWidth()和 getMeasuredHeight()都已经有值了。

Measure 分析到此为止。

layout过程分析

performTraversals ()方法调用performMeasure()计算出mMeasuredWidth和mMeasuredHeight后开始调用performLayout()来确定View具体放在哪个位置。
layout过程的主要作用 :根据子视图的大小以及布局参数将View放到合适的位置上。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

		...//其他代码
		
   }

看下ViewGroup 的layout()方法:

@Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

layout 的具体实现是在super.layout(l, t, r, b)里面做的,那么我接下来看一下View类的layout函数

public final void layout(int l, int t, int r, int b) {
     
       .....
       
      //设置View位于父视图的坐标轴
       boolean changed = setFrame(l, t, r, b); 
       //判断View的位置是否发生过变化,看有必要进行重新layout吗
       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }
           //调用onLayout(changed, l, t, r, b); 函数
           onLayout(changed, l, t, r, b);
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
       
       .....
       
   }

1、setFrame(l, t, r, b) 可以理解为给mLeft 、mTop、mRight、mBottom赋值,然后基本就能确定View自己在父视图的位置了,这几个值构成的矩形区域就是该View显示的位置,这里的具体位置都是相对与父视图的位置。
2、回调onLayout,对于View来说,onLayout只是一个空实现,一般情况下我们也不需要重载该函数:

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

    }

对于ViewGroup 来说,唯一的差别就是ViewGroup中多了关键字abstract的修饰,要求其子类必须实现onLayout函数:

@Override  
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

而实现onLayout()的目的就是安排其children在父视图的具体位置,那么如何安排子View的具体位置呢?
看下FrameLayout的onLayout():

//FrameLayout.java
 
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

	void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
	 
		int childCount = getChildCount() ; 
		
		...//其他代码
		
		for(int i=0 ;i<childCount ;i++){
		      final View child = getChildAt(i);
		      if (child.getVisibility() != GONE) {
			    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                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;
                }
                
			  	 ...//其他代码
		
		      	 //整个layout()过程就是个递归过程
		       	 child.layout(l, t, r, b) ;
		     }
		  }		
	}

代码很简单,就是遍历自己的孩子,然后根据gravity,padding,margin等布局文件里的参数,结合measure过程测量出的mMeasuredWidth和mMeasuredHeight,计算各个child的l, t, r, b值,最后调用 child.layout(l, t, r, b) 递归继续给child布局。

layout 过程相对简单些,分析就到此为止。

draw过程分析

performTraversals ()方法调用performLayout()计算出View具体放在哪个位置之后调用performDraw()进行绘制:

private void performDraw() {
		
		...//其他代码
		
		try {
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } 
        
		...//其他代码
 
 }

调用了draw()方法:

private boolean draw(boolean fullRedrawNeeded) {

	...//其他代码
	
   mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);

	...//其他代码

}

调用绘制线程ThreadedRenderer的draw()方法进行绘制,参数mView就是DecorView,之后调用View.java的draw()方法:

public void draw(Canvas canvas) {
    ...
        /*
         * 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)
         */

        // Step 1, draw the background, if needed
    ...
        background.draw(canvas);
    ...
        // skip step 2 & 5 if possible (common case)
    ...
        // Step 2, save the canvas' layers
    ...
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
    ...
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }
    ...
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
    }

注释写得比较清楚,一共分成6步,看注释:

// skip step 2 & 5 if possible (common case)

除了2 和 5之外我们一步一步来看:
第1步:绘制View的背景
看注释即可,不是重点

private void drawBackground(Canvas canvas) { 
     Drawable final Drawable background = mBackground; 
      ...... 
     //mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域 
     if (mBackgroundSizeChanged) { 
        background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);   
        mBackgroundSizeChanged = false; rebuildOutline(); 
     } 
     ...... 
     //调用Drawable的draw() 把背景图片画到画布上
     background.draw(canvas); 
     ...... 
}

第3步,绘制View的内容。
onDraw(canvas) 方法是view用来draw 自己的,具体如何绘制,颜色线条什么样式就需要子View自己去实现,View.java 的onDraw(canvas) 是空实现,ViewGroup 也没有实现,每个View的内容是各不相同的,所以需要由子类去实现具体逻辑。

第4步, 对View的所有子View进行绘制
dispatchDraw(canvas) 方法是用来绘制子View的。
View.java 的dispatchDraw()方法是一个空方法,因为View本身就是叶子节点,不需要实现dispatchDraw ()方法,ViewGroup就不一样了,它实现了dispatchDraw ()方法:

@Override
 protected void dispatchDraw(Canvas canvas) {
       
       ...
       
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
        
      ......
      
    }

dispatchDraw()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用drawChild()进行具体的绘制了,drawChild()方法实际调用的是子视图的draw()方法。

第6步 对View的滚动条进行绘制
看注释即可,不是重点

一张图看下整个draw的递归流程:

AndroidStudio 画流程图 安卓 画流程图_ide_14

到此整个绘制过程基本讲述完毕了。