欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android开发学习笔记——自定义View(一)View三大工作流程measure、layout和draw

程序员文章站 2022-05-16 14:34:03
...


Android已经为我们提供了众多基础控件,我们可以通过这些控件创建出多种多样的界面布局,但是,在实际开发中,如果界面元素过于复杂有可能无法使用基础控件实现,此时就需要我们根据需求自定义view。在正式开始学习自定义view的方法之前,我们需要对View体系有深入的理解,只有这样才能够写出好的自定义View,同时对View体系的理解能够帮助我们在开发中避免和修改view相关的bug。

View基本介绍

在学习View体系之前,首先,我们需要知道什么是View?View是Android中所有控件的基类,包括ViewGroup,它内部包含多个控件,即view组,比如我们常用的几大布局都属于ViewGroup。ViewGroup也继承自View类,也就是说View本身可以是单个控件也可以是多个控件组成的一组控件。我们可以查看TextView和LinearLayout的继承关系,如下图:
Android开发学习笔记——自定义View(一)View三大工作流程measure、layout和draw
Android开发学习笔记——自定义View(一)View三大工作流程measure、layout和draw
我们可以看到两者继承自View类,其中LinearLayout继承自ViewGroup,而ViewGroup继承自View,对于Android中View的层级关系,具体我们可以查看如下图:
Android开发学习笔记——自定义View(一)View三大工作流程measure、layout和draw

View的三大工作流程

View的工作流程主要是指measure、layout和draw这三大流程,即测量、布局和绘制,View通过measure测量view的宽高,layout确定view的最终宽高和位置,最后通过draw将View绘制到屏幕上。

measure过程

measure过程可以分为两种情况,如果只是一个View,那么通过measure方法即可完成其测量过程,如果是一个ViewGroup,除了完成自己的测量过程为,还需要遍历其子view并完成其measure过程,测量子view的宽高。

view的measure过程

View的measure过程是由其measure方法来完成的,measure方法是一个final类型的方法,子类无法重写,其中会调用View的onMeasure方法,因此,我们只需要看onMeasure方法的实现即可,其源码如下图:

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

我们可以看到,onMeasure方法实现非常简单,只是调用了setMeasuredDimension方法,其源码如下:

/**
     * <p>This method must be called by {@link #onMeasure(int, int)} to store the
     * measured width and measured height. Failing to do so will trigger an
     * exception at measurement time.</p>
     *
     * @param measuredWidth The measured width of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     * @param measuredHeight The measured height of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     */
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

从setMeasuredDimension方法的实现以及相关注释说明中,我们可以推断该方法用于设置view的宽高为measuredWidth和measuredHeight,那么具体其宽高为多少呢?我们需要继续查看getDefaultSize和getSuggestedMinimumWidth方法。其中getDefaultSize方法如下:

/**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

我们可以看到,getDefaultSize会根据specMode即测量模式来确定最终的返回结果,我们可以看到当specMode为MeasureSpec.UNSPECIFIED时,其返回结果为方法的第一个参数size,而这个参数是通过getSuggestedMinimumWidth获取到的,其源码如下:

/**
     * Returns the suggested minimum width that the view should use. This
     * returns the maximum of the view's minimum width
     * and the background's minimum width
     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
     * <p>
     * When being used in {@link #onMeasure(int, int)}, the caller should still
     * ensure the returned width is within the requirements of the parent.
     *
     * @return The suggested minimum width of the view.
     */
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

通过源码,我们可以看到getSuggestedMinimumWidth方法返回的值为view的最小值,当view设置了背景时返回背景Drawable的原始宽度(如果无原始宽度返回0)与mMinWidth,即android:minWidth的值中的最大值,否则就返回mMinWidth。
然后,我们再回过头来看MeasureSpec的具体实现。

MeasureSpec

MeasureSpec为View的一个内部类,其在很大程度上决定了一个View的尺寸规格,在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换为对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。
MeasureSpec代表了一个32位的int值,其中高2位代表SpecMode(测量模式),低30位代表SpecSize(测量规格大小)
对于MeasureSpec,我们首先需要主要了解其中的几个常量以及getMode和getSize方法:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
/**
 * Measure specification mode: The parent has not imposed any constraint
 * on the child. It can be whatever size it wants.
 */
public static final int UNSPECIFIED = 0 << MODE_SHIFT;

/**
 * Measure specification mode: 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.
 */
public static final int EXACTLY     = 1 << MODE_SHIFT;

/**
 * Measure specification mode: The child can be as large as it wants up
 * to the specified size.
 */
public static final int AT_MOST     = 2 << MODE_SHIFT;


public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

从上述代码中,我们可以了解到其工作模式,getMode方法获取到measureSpec的高2位为specMode,getSize获取到measureSpec的低30位为specSize。其中specMode包含了UNSPECIFIED、EXACTLY和AT_MOST三种模式,具体说明如下:

模式 说明
UNSPECIFIED 父容器不对view做任何限制,一般用于系统内部
EXACTLY 父容器已经检测出view所需要的精确大小,这个时候view的最终大小就为SpecSize,对应于LayoutParams中的match_parent和具体数值两种情况
AT_MOST 父容器指定了一个可用大小SpecSize,view的大小不能超过这个值,具体是多少要看View的具体实现,对应于LayoutParams中的wrap_content

从上述的getDefaultSize源码,我们可以看出,只要我们确定了view的measureSpec,我们就可以通过其specMode以及specSize确定其测量宽高。那么measureSpec是怎么确定的呢?

MeasureSpec和LayoutParams的对应关系

在view测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,也就是说view的MeasureSpec是由父容器的MeasureSpec以及view本身的LayoutParams共同决定的。
既然,view的MeasureSpec是受到父容器的MeasureSpec影响的,而view的measure过程是由ViewGroup传递而来的,我们可以查看ViewGroup中的measureChild源码:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

显然,child的measureSpec是通过getChildMeasureSpec根据parent的measureSpec和其本身的LayoutParams获取到的,再查看getChildMeasureSpec:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

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

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 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;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

通过上述代码,我们可以总结出view的MeasureSpec的创建规则大致如下图所示,其中parentSize为父容器剩余空间大小:
Android开发学习笔记——自定义View(一)View三大工作流程measure、layout和draw
此时,我们再回过头来看getDefaultSize方法,我们可以总结view的measure过程,大致如下:父容器在测量时确定了子view的measureSpec,通过measureSpec可以确定其测量宽高,当view的specMode为UNSPECIFIED时,其宽高为view的minWidth和background宽度的最大值,否则view的宽高为specSize。而从上述表格中,我们可以发现,当我们直接继承view来自定义控件时,我们需要重写onMeasure方法并设置wrap_content时的自身大小,否则布局中使用wrap_content就相当于match_parent,此时其specMode为AT_MOST,而其specSize为parentSize,也就是说此时view的大小为parentSize,我们可以重写onMeasure如下:

  @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
      // 在 MeasureSpec.AT_MOST 模式下,给定一个默认值mWidth,mHeight。默认宽高灵活指定
      //参考TextView、ImageView的处理方式
      //其他情况下沿用系统测量规则即可
    if (widthSpecMode == MeasureSpec.AT_MOST
            && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWith, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWith, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

ViewGroup的measure过程

对于ViewGroup而言,除了完成自己的measure过程以外,还需要遍历调用其子view的measure方法。ViewGroup中没有定义onMeasure()方法,但他定义了measureChildren()方法。

/**
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

我们可以看到,在measureChildren中遍历了ViewGroup的子view并对每个状态不为GONE的child调用了measureChild来进行其measure过程。而正如我们之前分析的measureChild方法的主要流程就是,取出子view的LayoutParams再结合父容器的MeasureSpec通过getChildMeasureSpec创建子view的MeasureSpec,然后将MeasureSpec直接传递个View的measure方法,完成子View的measure过程即可。代码如下:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

ViewGroup并没有定义其onMeasure方法的实现,因为其测量过程针对于不同的子类的不同布局特性实现不同,如LinearLayout和RelativeLayout等,具体可以查看其子类的源码。

layout过程

layout过程的作用是ViewGroup用于确定子元素的位置,当ViewGroup的位置被确定后,它再onLayout中会遍历所有的子元素并调用其layout方法,在子元素的layout方法中onLayout方法又会被调用。

/**
     * Assign a size and position to a view and all of its
     * descendants
     *
     * <p>This is the second phase of the layout mechanism.
     * (The first is measuring). In this phase, each parent calls
     * layout on all of its children to position them.
     * This is typically done using the child measurements
     * that were stored in the measure pass().</p>
     *
     * <p>Derived classes should not override this method.
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their children.</p>
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @SuppressWarnings({"unchecked"})
    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;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

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

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if (!wasLayoutValid && isFocused()) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            if (canTakeFocus()) {
                // We have a robust focus, so parents should no longer be wanting focus.
                clearParentsWantFocus();
            } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
                // layout. In this case, there's no guarantee that parent layouts will be evaluated
                // and thus the safest action is to clear focus here.
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                clearParentsWantFocus();
            } else if (!hasParentWantsFocus()) {
                // original requestFocus was likely on this view directly, so just clear focus
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            }
            // otherwise, we let parents handle re-assigning focus during their layout passes.
        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            View focused = findFocus();
            if (focused != null) {
                // Try to restore focus as close as possible to our starting focus.
                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                    // Give up and clear focus once we've reached the top-most parent which wants
                    // focus.
                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                }
            }
        }

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }

        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
    }

layout的四个参数代表了view到父容器的坐标位置,其中 l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离,r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。
其中关键代码如下:

boolean changed = isLayoutModeOptical(mParent) ?
        setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

通过setOpticalFrame和setFrame方法来设定View的位置,同时这两个方法最终都会进入setFrame方法,如下:

protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        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;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                // If we are visible, force the DRAWN bit to on so that
                // this invalidate will go through (at least to our parent).
                // This is because someone may have invalidated this view
                // before this call to setFrame came in, thereby clearing
                // the DRAWN bit.
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                // parent display list may need to be recreated based on a change in the bounds
                // of any child
                invalidateParentCaches();
            }

            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = true;
            mDefaultFocusHighlightSizeChanged = true;
            if (mForegroundInfo != null) {
                mForegroundInfo.mBoundsChanged = true;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        return changed;
    }

可以看到,setFrame通过更新mLeft、mRight、mTop和mBottom,并对之前和之后的位置进行重绘来达到改变view位置的目的。View在父容器中的位置确定后,接着会调用onLayout方法用于确定子元素的位置,和onMeasure相同,onLayout也是在ViewGroup的子类中实现的,我们可以了解下LinearLayout的onLayout方法,如下:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

layoutVertical和layoutHorizontal实现逻辑基本相同,我们只需要了解其中一个即可,查看layoutVertical源码如下:

void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;

        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;

        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) {
           case Gravity.BOTTOM:
               // mTotalLength contains the padding already
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;

               // mTotalLength contains the padding already
           case Gravity.CENTER_VERTICAL:
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;

           case Gravity.TOP:
           default:
               childTop = mPaddingTop;
               break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

我们可以看到,该方法遍历其所有状态不为GONE的子元素,并通过gravity、padding等一系列条件,计算出其相对位置,并调用setChildFrame方法来为子元素指定对应的位置,其中childTop为逐渐增大,这意味着子元素为被放置到底部,这符合LinearLayout的布局规则。查看setChildFrame方法,如下:

private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

我们发现这个方法仅仅是调用child的layout方法而已,这样父容器在通过layout完成自身的布局位置确定后,又会通过onLayout方法去调用子元素的layout方法来确定子元素的位置,这样逐一传递下去就最终完成了整个View树的layout过程。

draw过程

draw过程的作用就是将确定好位置和大小的View绘制到屏幕上面,通过draw方法的源码,我们可以很容易知道draw过程主要分为以下几步:

  1. 绘制背景
  2. 保存图层信息canvas(可跳过)
  3. 绘制view内容
  4. 绘制所有子元素children
  5. 绘制View的褪色的边缘,类似于阴影效果(可跳过)
  6. 绘制装饰Decoration(滚动条)
  7. 如果必要绘制焦点高亮显示
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * 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)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

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

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
        //.....省略

我们可以看到draw过程中通过onDraw来绘制view自身,onDraw就是我们在自定义view时最重要的方法,通过onDraw方法,我们可以绘制控件的具体实现,因此每个view的onDraw方法都是不同的。绘制好view后自身后,通过dispatchDraw来绘制其所有子元素,该方法在View中没有实现,在ViewGroup被实现,在该方法中遍历所有子元素并调用了drawchild方法,如下:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

我们可以看到调用了子view的draw方法,如此一来即可逐层传递绘制所有的view。