Android中View的绘制流程详解

View的绘制流程从根节点(DecorView)开始,自上而下,每个View的绘制流程分为三个部分,Measure,Layout,Draw。整个View的绘制流程从performTraversals()开始。

Android中View的绘制流程详解_第1张图片

Measure

   <span style="font-size:18px;"> public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

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

            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }

            // 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 & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }</span>

measure方法为final类型,但是最终会调用onMeasure()方法,所以在自定义的View中,只需override onMeasure方法就可以了。
measure函数的作用是,从上到下计算每个View的大小,并且每个视图存储自己尺寸的大小和测量规格。

几个核心函数

onMeasure

   <span style="font-size:18px;"> protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        <strong><span style="color:#ff0000;">setMeasuredDimension</span></strong>(<strong><span style="color:#ff0000;">getDefaultSize</span></strong>(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }</span>

在自定义的View中,通过重写该方法实现对View的测量,两个参数是父视图对子视图width和height 的要求。

setMeasuredDimension

<span style="font-size:14px;"><strong>  </strong> </span><span style="font-size:18px;"> protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }</span>

对View的长宽进行赋值。

getDefaultSize

<span style="font-size:18px;">    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;
    }</span>

getDefaultSize中涉及到MeasureSpec类。MeasureSpec的值由specSize和specMode两部分组成,specSize表示大小,specMode表示模式。specMode共有三种模式:

EXACTLY

父视图使用specSize来决定子视图的尺寸,对应属性为match_parent或者具体的数值比如200dp。

AT_MOST

父视图为子视图设定一个最大值,子视图只能在给定的specSize范围内,对应属性wrap_content。

UNSPECIFIED

父视图不对子视图进行任何的约束。这种情况比较少见,不太会用到。


一个Layout中通常会有多个View,每个View都需要进行measure。ViewGroup中定义了measureChildren()方法用来测量视图的大小。
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);
        }
    }
}

在方法中,首先去遍历当前布局下的子视图,每个子视图通过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);
}

Layout

layout
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 = setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
        }
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~LAYOUT_REQUIRED;
        if (mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT;
}

四个参数分别代表相对于父视图的左、上、右、下的坐标。setFrame()是用来判断View的大小是否发生过变化,进而判断是不是需要重绘。
layout的作用是用来确定子视图在父视图中的位置。
View的onLayout是空的,ViewGroup的onLayout是abstract的,所以自定义的View继承ViewGroup时必须实现onLayout方法。

onLayout
 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) {
    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();
<span style="white-space:pre">				</span>...
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                }
            }
    }
    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }    

    View.java
    public void layout(int l, int t, int r, int b) {
        ...
        setFrame(l, t, r, b)
    }
     protected boolean setFrame(int left, int top, int right, int bottom) {
         ...
     }

Draw

绘制的过程分为六部,见源码注释

public void draw(Canvas canvas) {

        / * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background if need
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children (dispatchDraw)
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

     <span style="white-space:pre">	</span>// Step 1, draw the background, if needed
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

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

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

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

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

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

        // Step 2, save the canvas' layers
        ...

        // 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

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

注意:
在这三个流程中Google已经把draw()过程框架写好了,自定义ViewGruop只需实现measure()即可。

你可能感兴趣的:(android,view,源码分析,详解,绘制View)