Android View视图绘制

前几篇事件分发中,我们已经了解了在Android中你所看到的界面的的分层情况Activity->DecorView->ViewGroup->View。本篇我们进入视图绘制的世界。

1、界面(View)结构图

看,经典tree结构
Android View视图绘制_第1张图片

2、简看系统View绘制流程

太详细的此处不多说了(主要是不能暴露自己水平太低…),简之,系统是通过ViewRoot(这里主要是ViewRootImpl)来实现DecorView的视图绘制的。

private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView;
    ...

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

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

    performLayout(lp, mWidth, mHeight);

    performDraw();

    ...
}

performMeasure 内部执行的也是 decorView.measure(childWidthMeasureSpec, childHeightMeasureSpec),而decorView是ViewGroup,又会循环是线子View.measure。在这里,我们脑中先有一个印象,View的绘制是measure->layout->draw。

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

3、MeasureSpec

public static class MeasureSpec {
    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;

    /**
     * Creates a measure specification based on the supplied size and mode.
     *
     * The mode must always be one of the following:
     * 
    *
  • {@link android.view.View.MeasureSpec#UNSPECIFIED}
  • *
  • {@link android.view.View.MeasureSpec#EXACTLY}
  • *
  • {@link android.view.View.MeasureSpec#AT_MOST}
  • *
* *

Note: On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.

* * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } /** * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } }

1、MeasureSpec 本身是表示父视图对子视图的描述、规定。由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格(模式)。是一个32位的int,前两位用于表示specMode,后30位用于表示specSize。make和get的过程都是通过位运算生成。
2、specMode的三种类型:

  • EXACTLY
    父视图已经决定了子视图的确切尺寸。不论子视图期望自己是多大,都会在限定的边界范围内。对应的子视图已设定的属性为 match_parent 或具体值,比如 100dp。
  • AT_MOST
    父视图已经给了一个最大尺寸。子视图最大不能超过这个specified size。对应属性为wrap_content。
  • UNSPECIFIED
    父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到。

4、View的onMeasure方法

先看经典的view绘制过程。
View绘制
首先进入View的measure方法,你会发现人家是final的,所以不用想什么了,但是里面调用了onMeasure(widthMeasureSpec, heightMeasureSpec),其中widthMeasureSpec、heightMeasureSpec是父视图对子试图(当前View)的测绘结果,规格要求。

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

getSuggestedMinimumWidth是View或background的最小宽度。然后通过getDefaultSize得到父类测量好的大小。

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

之后通过setMeasuredDimension()方法来设定测量出的大小,这样一次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);
}

其中

** 
 * 该方法是 measureChildren 中最繁重的部分,为每一个 ChildView 计算出自己的 MeasureSpec。
 * 目标是将 ChildView 的 MeasureSpec 和 LayoutParams 结合起来去得到一个最合适的结果。
 *
 * @param spec 对该 View 的测绘要求
 * @param padding 当前 View 在当前唯独上的 paddingand,也有可能含有 margins
 *
 * @param childDimension 在当前维度上(height 或 width)的具体指
 * @return 子视图的 MeasureSpec 
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

        .........

    // 根据获取到的子视图的测量要求和大小创建子视图的 MeasureSpec
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}

5、Layout过程

View中layout的作用是ViewGroup用来确定子视图(子View)的位置,当ViewGroup的位置被确定后,它在onLayout中遍历所有子元素,执行各自的layout的方法;如果子元素是ViewGroup,将在固定好自己的位置后继续调用onlayout方法,一直循环下去。
如下View的Layout方法(简):

public void layout(int l, int t, int r, int b) {
    ***
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //1.setFrame()设置此View在父视图中的坐标位置
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //2.如果是ViewGroup,执行onLayout
        onLayout(changed, l, t, r, b);

        ***
    }

    ***
}

其实当我们去查看ViewGroup中的onLayout时,发现也是一个抽象方法,那我们可以参考LinearLayout

@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

void layoutVertical(int left, int top, int right, int bottom) {
    ...
    final int count = getVirtualChildCount();
    ...

    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();//measure 过程确定的 Width
            final int childHeight = child.getMeasuredHeight();

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

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

            childTop += lp.topMargin;
            //确定 childLeft、childTop 的值
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

可以看到,layoutVertical就是遍历所有(非Gone)的view,并调用setChildFrame来确定子元素的位置

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

最终执行子view的layout的方法,子view在layout方法中执行setFrame确定自己的位置,同时如果子View时一个ViewGroup则继续执行onLayout方法。

此处有个点,我们常说getMeasuredWidth“通常”等于getWidth,为什么时“通常”呢,有特殊情况吗?

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

带getMeasuredWidth是指测量宽高,getWidth指最终宽高。如最终的宽是通过mRight - mLeft得到的,如上方layoutVertical中,setChildFrame(child, childLeft+100, childTop + getLocationOffset(child),
childWidth, childHeight),我们将其中的参数改变下,岂不是就有可能不等于了。

6、Draw过程

View的draw总共6步,但是我们一般只需要知道四步必须的。
1. 绘制背景drawBackground(canvas)
2. 绘制自己onDraw(canvas)
3. 绘制children,dispatchDraw(canvas)
4. 绘制装饰(foreground, scrollbars)

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
    int saveCount;

    if (!dirtyOpaque) {
        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
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(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);

        // 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 (foreground, scrollbars)
    onDrawForeground(canvas);
}

另外

  • ViewGroup中没有重写draw,因此所有的子视图都是调用自身的draw方法进行绘制。自定义视图时,也不建议override此方法,一般重写onDraw即可,如果非要使用,则需要在重写draw时,加上super.draw(canvas),先执行系统绘制,然后执行自定义内容。
  • dispatchDraw() 发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。自定义的 ViewGroup 不应该对dispatchDraw()进行重写。
  • invalidate()
    请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的 View。
    • requestLayout()
      当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发measure()和layout()过程,但不会进行 draw。

你可能感兴趣的:(Android,基础,视图绘制,源码分析,android)