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

1、界面(View)结构图

看,经典tree结构

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

@Overrideprotected 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。

更多相关文章

  1. Android:你要的WebView与 JS 交互方式 都在这里了
  2. android性能优化之布局优化
  3. [Android] 利用java反射调用隐藏Api
  4. Android(安卓)中几种更新UI界面的方法
  5. Android高手进阶教程(十七)之---Android中Intent传递对象的两种
  6. 安卓(Android)系统的检测方法
  7. Android(安卓)面试题(1)
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. android 几个阶段
  2. Android在全球的市场份额跃居全球第一
  3. android中常见的四种布局
  4. Android(安卓)Studio开发JNI - 第一个JNI
  5. 自己归纳 Android(安卓)四种布局简介
  6. 作业三——观察者模式在Android广播机制
  7. ListPreference之entries和entryValues
  8. ListPreference之entries和entryValues
  9. 安卓自动完成文本框(autoCompleteTextVie
  10. cocos2d-2.1rc0-x-2.1.3建android工程Hel