View的绘制流程梳理

View的绘制流程梳理

MeasureSpec

由于在绘制过程中,这个变量作为参数,与所有的View的measure都有关系。
MeasureSpec是一个int值,分为两个部分,高两位代表SpecMode,紧接着的30位代表着SpecSize。以下是其计算过程:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;
public static final int AT_MOST     = 2 << MODE_SHIFT;

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
}

当然,有打包的方法也会有解包的方法,我们可以通过getMode()和getSize()分别来获取SpecMode和SpecSize。

其中SpecMode分为三种:

UNSPECIFIED

ParentView 对ChildView的大小没有任何约束,ChildView的大小可以为任意值。

EXACTLY

ParentView 已经检测到ChildView所需要的精确的大小,而ChildView的最终大小也就是MeasureSpec指定的值。

AT_MOST

ParentView 为ChildView指定了一个上限值,ChildView的大小可以在这个范围内变化。

MeasureSpec的来源

MeasureSpec最初是在顶级View中创建的,顶级View(即DecorView)的MeasureSpec是由窗口的尺寸和其自身的LayoutParams决定的。其创建过程在ViewRootImp中的measurehierarchy方法里:

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

其中desiredWindowWidth 和 desiredWindowHeight是屏幕的尺寸,而lp就是DecorView自身的layoutParams,来自ViewRootImp中一个成员变量mWindowAttributes。接下来看一下getRootMeasureSpec的逻辑:

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

通过上诉代码我们得出,对于DecorView来说,其SpecMode主要是自身的layoutParams来决定的,当其为MATCH_PARENT或者其他指定的值时,SpecMode为EXACTLY,当其为WRAP_CONTENT,SpecMode为AT_MOST。对于SpecSize,当不指定具体值的时候,我们就使用WindowSize来作为MeasureSpec的一部分,否则,使用指定的值。

对于普通View来说,其Measure是由ParentView的MeasureSpec和其自身的Layoutparams来决定,具体的逻辑是在ViewGroup的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 = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

方法中的三个参数分别为:spec:当前Viewgroup的measureSpec的值;padding:ViewGroup已经占用的大小;childDimension:在当前的尺寸下,子View想要的大小,也就是子View的LayoutParams。上面的代码可以简单用 一张表格来表示:

EXACTLYAT_MOSTUNSPECIFIED
MATCH_PARENTSpecSize = size SpecMode = EXACTLYSpecSize =size SpecMode = AT_MOSTSpecSize = 0 SpecMode=UNSPECIFIED
WRAP_CONTENTSpecSize =size SpecMode = AT_MOSTSpecSize =size SpecMode = AT_MOSTSpecSize = 0 S pecMode = UNSPECIFIED
exactly sizeSpecSize=childDimension Specmode =EXACTLYSpecSize=childDimension Specmode =EXACTLYSpecSize=childDimension Specmode =EXACTLY

Measure

View的绘制过程是从ViewRootImp中的performTraversals()开始,其中分别调用performMeasure(),performLayout(),以及performDraw(),同时也调用起了measureHierarchy方法。而之前在上文中已经说过,该方法会去设置DecorView的MeasureSpec,并且调用performMeasure()方法。

 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

这里调用的是DecorView的measure方法,DecorView作为顶级View,其内部一般包含一个LinearLayout,其中又包含上下两个部分,一个titlebar,一个是content。而Activity中的setContentView就是将我们的布局填充到Content中。

在这里插入图片描述

下面是View的measure()的代码

    /**
     * <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) {
      boolean optical = isLayoutModeOptical(this);
       if (optical != isLayoutModeOptical(mParent)) {
          Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
           heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
      }
       if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
               widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }
 /**
     * Return true if o is a ViewGroup that is laying out using optical bounds.
     * @hide
     */
    public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
   }

通过注释我们可以知道,一个View的具体测量是在onMeasure中做的。而onMeasure的代码如下:

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

其中getDefaultSize()和getSuggestedMinimumWidth()的代码如下:

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}


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

getSuggestedMinimumWidth最终得到的是这个View中是否有背景,如果没有就返回mMinWidth,而这个值对应着android:minWidth,一般默认为0。否则的话,就取背景的最小宽度和mMinWidth两者的最大值。

getDefaultSize的逻辑也很好理解,如果specMode为AT_MOST或者EXACTLY,就取ParentView(屏幕)能给到的最大值,否则的话,就取getSuggestedMinimumWidth返回的值。结合之前DecorView的MeasureSpec的来源,我们可以发现,其中SpecMode就只有两种模式,AT_MOST或者EXACTLY,所以specSize决定了DecorView的宽和高。但是这里也存在一个问题,那就是WRAP_CONTENT和MATCH_PARENT,表现为一致了,所以我们在自定义View的时候,重写onMeasure时,要针对AT_MOST有其他的设计。

整个View 的measure过程是层层递归的,在DecorView的measure过程结束之后,会调用起ViewGroup的measure过程,然后在开始子View的measure。

ViewGroup的measure过程主要是对子View的一个递归measure的过程,首先是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);
            }
        }
    }

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    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);
    }
		
	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);
    }
    

这部分的d代码很好理解,逐一遍历View树的每一个节点,并且让其执行 measure动作,如果子View是ViewGroup,那么会继续执行measureChildren()。

Layout

layout的调用时在ViewRootImp中的PerformTraversal开始被调用的,VIew的layout过程也是和View的Measure过程相似,是一个递归的过程。

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

这里就是传入四个顶点的坐标作为参数,接下来我们先看view的layout函数:

   /**
     * 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) {
        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);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo o = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>) li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    }





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

        if (DBG) {
            Log.d("View", 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;
            if (mDisplayList != null) {
                mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
            }

            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            if (sizeChanged) {
                if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
                    // A change in dimension means an auto-centered pivot point changes, too
                    if (mTransformationInfo != null) {
                        mTransformationInfo.mMatrixDirty = true;
                    }
                }
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // 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;
        }
        return changed;
    }

首先判断布局与之前是否有变化,如果有变化或者是调用了requesLayout(),就调用onLayout(),判断布局是否有变化的逻辑也比较简单,主要是判断四个顶点大小是否有区别,同时在setFrame()中不仅仅判断了布局是否变化,同时也将mLeft,mRight,mTop,mBottom赋值。在调用 onLayout()之后,就会调用OnLayoutChangeListener.onLayoutChange()。

但是这里有一个值得注意的点就是,我们想要得到最终View的宽和高的时候,必须等待setFrame()执行完,因为View.getHeight()以及View.getWidth()是通过mLeft,mRight,mTop,mBottom这四个值来得到最终宽和高的。因为存在测量得到的宽和高并不一定最后原封不动作为参数传入layout()中,所以measure得到的宽和高不一定是最终值,这样的话getMeasuredHeight()以及getMeasuredWidth()获得的值是会存在会与View.getHeight()以及View.getWidth()不等的情况。同时参照上面的代码,我们可以在onLayoutChange()中调用getWidth()和getHeight()来获取。

    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

    public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

    public final int getWidth() {
        return mRight - mLeft;
    }

    public final int getHeight() {
        return mBottom - mTop;
    }

    public final int getLeft() {
        return mLeft;
    }

    public final int getRight() {
        return mRight;
    }

    public final int getTop() {
        return mTop;
    }

    public final int getBottom() {
        return mBottom;
    }

再来看ViewGroup的layout函数,最终也是调用到View的layout()中,逻辑没什么区别。

  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过程中,我们的最终目的是获取View的最终四个顶点的值,所以View的onLayout方法全部为空,并且不要求子类一定要去实现它,比如:ImageView中就没有实现onLayout,而textView的onlayout执行的也是跟这四个顶点的确认没有关系,因为View的layout()已经将其实现了。但是ViewGrop中的子View不一样,因为需要计算每个子view的四个顶点的位置,所以VIewGroup的onlayout()是一个抽象方法,这要求所有VIewGroup的子类需要去实现这个方法,其中需要执行的操作一定是计算其所有子View的四个顶点的位置,我们可以用LinearLayout的onlayout()来举例:

public class LinearLayout extends ViewGroup {
    @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);
        }
    }
}


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

Draw

Draw的流程如下:

其中,View的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
        ......
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

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

        // Step 2, save the canvas' layers
        ......
            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);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        ......

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        ......
    }

大概分为四步:

(1) 绘制背景:drawBackground(canvas);

(2)绘制自己:onDraw(canvas);

(3)绘制事件分发:dispatchDraw(canvas);

(4)绘制装饰:onDrawScrollBars(canvas);

如此,从顶层 DecorView 的 draw 方法开始,然后调用 dispatchDraw 方法循环遍历绘制子元素,如果子元素是继承了 ViewGroup ,就再次循环调用 dispatchDraw 方法,一层层往下递归调用,直到每一个子元素都被绘制完成,整个 draw 流程也就结束了。

WillNotDraw

在 View 中有一个方法是 setWillNotDraw:

//View 类
/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

当一个View不需要draw的时候,就可以将mViewFlags设置为true,这样的话系统会进行相应的优化,比如ViewGroup就不需要draw这一个步骤,所以就会默认使用setWillNotDraw(true)来完成这一步优化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值