Android View 绘制源码分析

本篇文章主要内容是:详细讲述View的测量(Measure)流程从ViewRootImpl#performTraversals说起

先来看看performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)这个方法,它传入两个参数,分别是childWidthMeasureSpec和childHeightMeasure,那么这两个参数代表什么意思呢?要想了解这两个参数的意思,我们就要先了解MeasureSpec。

MeasureSpec

 

其中,测量模式(Mode)的类型有3种:UNSPECIFIED、EXACTLY 和 
AT_MOST。具体如下:


对于每一个View,包括DecorView,都持有一个MeasureSpec,而该MeasureSpec则保存了该View的尺寸规格。

在View的测量流程中,通过makeMeasureSpec来保存宽高信息,在其他流程通过getMode或getSize得到模式和宽高。

那么问题来了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影响的,那么,对于DecorView来说,它已经是顶层view了,没有父容器,那么它的MeasureSpec怎么来的呢?

是根据window的窗口大小而来。回到ViewRootImpl#performTraversals


 

根据不同的模式来设置MeasureSpec,如果是LayoutParams.MATCH_PARENT模式,则是窗口的大小,WRAP_CONTENT模式则是大小不确定,但是不能超过窗口的大小.默认强制设置为窗口大小。

到了这里,我们就为顶级的DecorView设置了MeasureSpec,它代表着根View的规格、尺寸,在接下来的measure流程中,就是根据已获得的根View的MeasureSpec来逐层测量各个子View。

ViewRootImpl#performTraversals

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
 void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
try {
            //调用了View#measure 方法,childWidthMeasureSpec 和childHeightMeasureSpec 是我们刚刚调用getRootMeasureSpec 生成的
//调用了View#measure 方法,childWidthMeasureSpec 和childHeightMeasureSpec 是我们刚刚调用getRootMeasureSpec 生成的
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
Trace.traceEnd(Trace.TRACE_TAG_VIEW

方法很简单,直接调用了mView.measure,这里的mView就是DecorView,也就是说,从顶级View开始了测量流程,那么我们直接进入measure流程。

由于DecorView继承自FrameLayout,是PhoneWindow的一个内部类,而FrameLayout没有measure方法,因此调用的是父类View的measure方法,我们直接看它的源码,

View#measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
if (optical != isLayoutModeOptical(mParent)) {
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
heightMeasureSpec != mOldHeightMeasureSpec) {
               int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
mMeasureCache.indexOfKey(key);
//如果我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure//如果我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure
 if (cacheIndex < 0 || sIgnoreMeasureCache) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
// measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else{
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
long) mMeasuredHeight & 0xffffffffL); // suppress sign extension

可以看到,调用到下一个关键函数onMeasure。由于DecorView是View子类,因此它实际上调用的是DecorView#onMeasure方法.


 @Override
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final boolean isPortrait =
final boolean isPortrait =
                getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;  //是否是竖向
getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;  //是否是竖向
        //获取高宽的模式
      //获取高宽的模式
        final int widthMode = getMode(widthMeasureSpec);  
final int widthMode = getMode(widthMeasureSpec);  
        final int heightMode = getMode(heightMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);
      //.....中间依据window的高宽,进行了很多判断,是否需要重新计算宽度和高度 
//.....中间依据window的高宽,进行了很多判断,是否需要重新计算宽度和高度 
        if (measure) {
if (measure) {
            //调用到了FrameLayout的onMeasure
  //调用到了FrameLayout的onMeasure
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec)

View有非常多的子类容器:FrameLayout、 RelativeLayout、LinearLayout。

FrameLayout#messure

    @Override@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       //获取子View的个数//获取子View的个数
        int count = getChildCount();int count = getChildCount();
        // 判断当前布局的宽高是否是wrap_content,如果是则置measureMatchParentChildren为true.后续需要再次测量      // 判断当前布局的宽高是否是wrap_content,如果是则置measureMatchParentChildren为true.后续需要再次测量
        final boolean measureMatchParentChildren =final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();mMatchParentChildren.clear();
        int maxHeight = 0;int maxHeight = 0;
        int maxWidth = 0;int maxWidth = 0;
        int childState = 0;int childState = 0;
        //遍历所有类型不为GONE的子View      //遍历所有类型不为GONE的子View
        for (int i = 0; i < count; i++) {for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {if (mMeasureAllChildren || child.getVisibility() != GONE) {
              对每一个子View进行测量, 依次往下层子View调用对每一个子View进行测量, 依次往下层子View调用
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
               //寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性//寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性
               //那么它的大小取决于子View中的最大者//那么它的大小取决于子View中的最大者
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());childState = combineMeasuredStates(childState, child.getMeasuredState());
              //如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加//如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
            //宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响//宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
                if (measureMatchParentChildren) {if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);mMatchParentChildren.add(child);
        // Account for padding too// Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
        // Check against our minimum height and width// Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // Check against our foreground's minimum height and width// Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();final Drawable drawable = getForeground();
        if (drawable != null) {if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
      //保存宽度和高度,必须要调用的方法    //保存宽度和高度,必须要调用的方法
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));childState << MEASURED_HEIGHT_STATE_SHIFT));
      //对所有match_parent的子View进行重新计算高度和宽度//对所有match_parent的子View进行重新计算高度和宽度
        count = mMatchParentChildren.size();count = mMatchParentChildren.size();
        if (count > 1) {if (count > 1) {
            for (int i = 0; i < count; i++) {for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                /**              /**
              * 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:* 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
              * 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:* 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
              * 对于子View来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度* 对于子View来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
              * 减去padding和margin后剩下的空间。* 减去padding和margin后剩下的空间。
               **/**/
                final int childWidthMeasureSpec;final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);- lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);width, MeasureSpec.EXACTLY);
                } else {else {
                  /** /** 
              * 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:* 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式* SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
              * * 
              * 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:* 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式* SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
              * #getChildMeasureSpec* #getChildMeasureSpec
              */*/
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,lp.leftMargin + lp.rightMargin,
                            lp.width);lp.width);
                final int childHeightMeasureSpec;final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);- lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);height, MeasureSpec.EXACTLY);
                } else {else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,lp.topMargin + lp.bottomMargin,
                            lp.height);lp.height);
                //重新计算后的width 和 height 重新测量//重新计算后的width 和 height 重新测量
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

 

measureChildWithMargins

  protected void measureChildWithMargins(View child,protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMarginmPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);+ widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMarginmPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);+ heightUsed, lp.height);
        //调用View的 measure方法,然后是child#onMeasure      //调用View的 measure方法,然后是child#onMeasure
      //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
      // 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

 

让我们看看getChildMeasureSpec 方法,

 * Does the hard part of measureChildren: figuring out the MeasureSpec to* Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec* pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.* for one dimension (height or width) of one child view.
     **
     * The goal is to combine information from our MeasureSpec with the* The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,* LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of* if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants* EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to* to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.* layout given an exact size.
     **
     * @param spec The requirements for this view* @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and* @param padding The padding of this view for the current dimension and
     *        margins, if applicable*        margins, if applicable
     * @param childDimension How big the child wants to be in the current* @param childDimension How big the child wants to be in the current
     *        dimension*        dimension
     * @return a MeasureSpec integer for the child* @return a MeasureSpec integer for the child
     */*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec); //父容器的模式int specMode = MeasureSpec.getMode(spec); //父容器的模式
        int specSize = MeasureSpec.getSize(spec);int specSize = MeasureSpec.getSize(spec);
        int size = Math.max(0, specSize - padding);  //父容器提供的大小int size = Math.max(0, specSize - padding);  //父容器提供的大小
        int resultSize = 0;int resultSize = 0;
        int resultMode = 0;int resultMode = 0;
        switch (specMode) {switch (specMode) {
        // Parent has imposed an exact size on us// Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {if (childDimension >= 0) {
                resultSize = childDimension;resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.// Child wants to be our size. So be it.
                resultSize = size;resultSize = size;
                resultMode = MeasureSpec.EXACTLY;resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be// Child wants to determine its own size. It can't be
                // bigger than us.// bigger than us.
                resultSize = size;resultSize = size;
                resultMode = MeasureSpec.AT_MOST;resultMode = MeasureSpec.AT_MOST;
            break;break;
        // Parent has imposed a maximum size on us// Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {if (childDimension >= 0) {
                // Child wants a specific size... so be it// Child wants a specific size... so be it
                resultSize = childDimension;resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.// Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.// Constrain child to not be bigger than us.
                resultSize = size;resultSize = size;
                resultMode = MeasureSpec.AT_MOST;resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be// Child wants to determine its own size. It can't be
                // bigger than us.// bigger than us.
                resultSize = size;resultSize = size;
                resultMode = MeasureSpec.AT_MOST;resultMode = MeasureSpec.AT_MOST;
            }
            break;break;
        // Parent asked to see how big we want to be// Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {if (childDimension >= 0) {
                // Child wants a specific size... let him have it// Child wants a specific size... let him have it
                resultSize = childDimension;resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should// Child wants to be our size... find out how big it should
                // be// be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how// Child wants to determine its own size.... find out how
                // big it should be// big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;break;
        }
        //noinspection ResourceType//noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

 

View#messure小结

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

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

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

 

 

通过以上代码分析,我们可以得出在FrameLayout中,如果子View的高或者宽设置成为了match_parent 其messure会被调用两次。

第一次是为了获取最大的高和宽。

第二次是为了给子View赋予精确的高宽值。

 

让我们继续下一步:

ViewRootImpl#performLayout

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {int desiredWindowHeight) {
        mLayoutRequested = false;mLayoutRequested = false;
        mScrollMayChange = true;mScrollMayChange = true;
        mInLayout = true;mInLayout = true;
        final View host = mView;final View host = mView;
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {try {
          //进入View的Layout阶段//进入View的Layout阶段
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mInLayout = false;mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.// requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.// If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do// If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.// a full request/measure/layout pass to handle this situation.
              //如果在布局期间,重新发起了requestLayout, 需要再重新测量、和布局。。 重复这个阶段//如果在布局期间,重新发起了requestLayout, 需要再重新测量、和布局。。 重复这个阶段
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);false);
                if (validLayoutRequesters != null) {if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during// Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next// the second pass, which may result in posting those requests to the next
                    // frame instead// frame instead
                    mHandlingLayoutInLayoutRequest = true;mHandlingLayoutInLayoutRequest = true;
                    // Process fresh layout requests, then measure and layout// Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");" during layout: running second layout pass");
                        view.requestLayout();view.requestLayout();
                    }
                  
                    measureHierarchy(host, lp, mView.getContext().getResources(),measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                  ....

 

我们再跟进DecorView#layout方法,由于DecorView是ViewGroup的间接子View所以,实现在ViewGroup的layout

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

 

View#layout

 public void layout(int l, int t, int r, int b) {public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { //测量发生了变化,需要重新测量,if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { //测量发生了变化,需要重新测量,
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
  
        int oldL = mLeft;int oldL = mLeft;
        int oldT = mTop;int oldT = mTop;
        int oldB = mBottom;int oldB = mBottom;
        int oldR = mRight;int oldR = mRight;
        boolean changed = isLayoutModeOptical(mParent) ?boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //布局发生变化,或者要去layout 。调用onLayout  //布局发生变化,或者要去layout 。调用onLayout
            onLayout(changed, l, t, r, b);onLayout(changed, l, t, r, b);
            if (shouldDrawRoundScrollbar()) {if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {else {
                mRoundScrollbarRenderer = null;mRoundScrollbarRenderer = null;
            }
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            ListenerInfo li = mListenerInfo;ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

从上面代码可以看出,在layout中,又调用了onLayout方法。同样,我们继续看看DecorView的onLayout方法

在DecorView#onLayout 中,调用的是super.onLayout. 其父类是FrameLayout。看看FrameLayout#onLayout

 @Override@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();final int count = getChildCount();
        final int parentLeft = getPaddingLeftWithForeground();final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();final int parentRight = right - left - getPaddingRightWithForeground();
        final int parentTop = getPaddingTopWithForeground();final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        for (int i = 0; i < count; i++) {for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //.... 依据子View的gravity计算子View的坐标。循环调用子View进行布局坐标计算              //.... 依据子View的gravity计算子View的坐标。循环调用子View进行布局坐标计算
                child.layout(childLeft, childTop, childLeft + width, childTop + height);child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

 

好了,这里就完成了布局的坐标计算。我们再次回到ViewRootIml,分析下一个阶段 绘制 performOnDraw

ViewRootIml#performOnDraw

 private void performDraw() {private void performDraw() {
       ....
        
            draw(fullRedrawNeeded);draw(fullRedrawNeeded);
      
        if (mReportNextDraw) {if (mReportNextDraw) {
          ...
           
            try {try {
                mWindowSession.finishDrawing(mWindow);mWindowSession.finishDrawing(mWindow);
            } catch (RemoteException e) {catch (RemoteException e) {
            }
        }
    }

 

可以看到,调用到了 draw(fullRedrawNeeded)方法,进行跟进

 private void draw(boolean fullRedrawNeeded) {private void draw(boolean fullRedrawNeeded) {
 ... 此处省略一大段源码,此处省略一大段源码,
     scrollToRectOrFocus(null, false);scrollToRectOrFocus(null, false);
 ....
    if (fullRedrawNeeded) {if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
   if(!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty){if(!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty){
     if(mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()){if(mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()){
       //支持硬件加速,硬件加速进行绘制//支持硬件加速,硬件加速进行绘制
        mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
     }else{else{
       ....
         //软件绘制//软件绘制
         if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;return;
                }
     }
       
 }

好,我们继续跟进drawSoftware 方法:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {boolean scalingRequired, Rect dirty) {
        // Draw with software renderer.// Draw with software renderer.
        final Canvas canvas;final Canvas canvas;
        try {try {
            final int left = dirty.left;final int left = dirty.left;
            final int top = dirty.top;final int top = dirty.top;
            final int right = dirty.right;final int right = dirty.right;
            final int bottom = dirty.bottom;final int bottom = dirty.bottom;
            //我们常见的canvas画布终于出现了          //我们常见的canvas画布终于出现了
            canvas = mSurface.lockCanvas(dirty);canvas = mSurface.lockCanvas(dirty);
        .......
            // If this bitmap's format includes an alpha channel, we// If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will// need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent// properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region// background. This automatically respects the clip/dirty region
            // or// or
            // If we are applying an offset, we need to clear the area// If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage// where the offset doesn't appear to avoid having garbage
            // left in the blank areas.// left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            dirty.setEmpty();dirty.setEmpty();
            mIsAnimating = false;mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;mView.mPrivateFlags |= View.PFLAG_DRAWN;
            try {try {
                canvas.translate(-xoff, -yoff);canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;attachInfo.mSetIgnoreDirtyState = false;
                //终于找到正主了!! , 看,进入到View的draw方法了!!              //终于找到正主了!! , 看,进入到View的draw方法了!!
                mView.draw(canvas);mView.draw(canvas);
                drawAccessibilityFocusedDrawableIfNeeded(canvas);drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {finally {
                if (!attachInfo.mSetIgnoreDirtyState) {if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call// Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {finally {
            try {try {
              //绘制完成,解锁canvas//绘制完成,解锁canvas
                surface.unlockCanvasAndPost(canvas);surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {catch (IllegalArgumentException e) {
           ....
        }
        return true;return true;
    }

从上面代码,我们终于找到View#draw的调用了。

View#draw

 @CallSuper@CallSuper
    public void draw(Canvas canvas) {public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        /*/*
         * Draw traversal performs several drawing steps which must be executed* Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:* in the appropriate order:
         **
         *      1. Draw the background*      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading*      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content*      3. Draw view's content
         *      4. Draw children*      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers*      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)*      6. Draw decorations (scrollbars for instance)
         */*/
        // Step 1, draw the background, if needed// Step 1, draw the background, if needed
        int saveCount;int saveCount;
        if (!dirtyOpaque) {if (!dirtyOpaque) {
            drawBackground(canvas);drawBackground(canvas);
        }
        // skip step 2 & 5 if possible (common case)// skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content// Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);if (!dirtyOpaque) onDraw(canvas);
            // Step 4, draw the children// Step 4, draw the children
            dispatchDraw(canvas);dispatchDraw(canvas);
            // Overlay is part of the content and draws beneath Foreground// Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);mOverlay.getOverlayView().dispatchDraw(canvas);
            }
            // Step 6, draw decorations (foreground, scrollbars)// Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);onDrawForeground(canvas);
            // we're done...// we're done...
            return;return;
        }

注释写得比较清楚,一共分成6步,看到注释没有( // skip step 2 & 5 if possible (common case))除了2 和 5之外 我们一步一步来看:

第一步:背景绘制

主要是根据高宽和滚动位置,绘制背景区域

    private void drawBackground(Canvas canvas) {private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;final Drawable background = mBackground;
        if (background == null) {if (background == null) {
            return;return;
        }
        setBackgroundBounds();setBackgroundBounds();
       ...
        final int scrollX = mScrollX;final int scrollX = mScrollX;
        final int scrollY = mScrollY;final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {if ((scrollX | scrollY) == 0) {
            background.draw(canvas);background.draw(canvas);
        } else {else {
            canvas.translate(scrollX, scrollY);canvas.translate(scrollX, scrollY);
            background.draw(canvas);background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);canvas.translate(-scrollX, -scrollY);
        }
    }

 

第二步、绘制边缘阴影 (可忽略)

 

第三步、绘制内容

<em>// Step 3, draw the content</em>
        if (!dirtyOpaque) onDraw(canvas);if (!dirtyOpaque) onDraw(canvas);

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

第四步、绘制子View

dispatchDraw(canvas) 方法是用来绘制子View的,View.java 的dispatchDraw()方法是一个空方法,因为View没有子View,不需要实现dispatchDraw ()方法,ViewGroup就不一样了,它实现了dispatchDraw ()方法:

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

遍历子View然后drawChild(),drawChild()方法实际调用的是子View.draw()方法,ViewGroup类已经为我们实现绘制子View的默认过程,这个实现基本能满足大部分需求,所以ViewGroup类的子类(LinearLayout,FrameLayout)也基本没有去重写dispatchDraw方法,我们在实现自定义控件,除非比较特别,不然一般也不需要去重写它, drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制了。

 

第五步、绘制边缘消失效果 可忽略

第六步、绘制滚动条, 其实就是绘制一个drawable

 

小结上诉的6步,整个draw的递归流程

 

总结:一张图描述View的测量、布局、绘制过程。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值