Android知识架构 · 电话面试 · View的绘制流程

来源:互联网 发布:创业怎么找投资人 知乎 编辑:程序博客网 时间:2024/06/05 05:03

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

各步骤的主要工作:

  • OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

  • OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

  • OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

OnMeasure

View树的绘制是从ViewRoot的performTraversals()方法开始,这个方法的主要作用是判断是否重新measure、是否重新layout、是否重新draw。

    private void performTraversals() {        ......        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);        ......        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        ......        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());        ......        mView.draw(canvas);        ......    }    private int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {            case ViewGroup.LayoutParams.MATCH_PARENT:                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);                break;            case ViewGroup.LayoutParams.WRAP_CONTENT:                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);                break;            default:                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);                break;        }        return measureSpec;    }     public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {            mPrivateFlags &= ~MEASURED_DIMENSION_SET;            if (ViewDebug.TRACE_HIERARCHY) {                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);            }            onMeasure(widthMeasureSpec, heightMeasureSpec);            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;    //View的onMeasure默认实现方法,设置View的长和宽;如果写死,则外边变化时界面不变化    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }    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;    }  

如果是ViewGroup,还应该进行嵌套测量:

    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);            }        }    }    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) {            case MeasureSpec.EXACTLY:                if (childDimension >= 0) {                    resultSize = childDimension;                    resultMode = MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    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;            ......        }        //将mode与size通过MeasureSpec方法整合为32位整数返回        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

MeasureSpec 的值由specSize和specMode共同组成的,高2位代表specMode,低30代表spceSize,其中specSize记录的是大小,specMode记录的是规格。

specMode一共有三种类型:

  1. EXACTLY
    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  2. AT_MOST
    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  3. UNSPECIFIED
    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见。

每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

OnLayout

测量完各个组件的大小之后,就可以排列他们的位置了。

    private void performTraversals() {        ......        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);        ......        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        ......        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());        ......        mView.draw(canvas);        ......    }    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;    }  

View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

@Override  /***ViewGroupd的onLayout方法*/protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  

然而,ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

下面代码以LinearLayout举例:

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;        final int width = right - left;        int childRight = width - mPaddingRight;        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:                childTop = mPaddingTop + bottom - top - mTotalLength;                break;            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);            }        }    }

到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:

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

OnDraw

public void draw(Canvas canvas) {      if (ViewDebug.TRACE_HIERARCHY) {          ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);      }      final int privateFlags = mPrivateFlags;      final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&              (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);      mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;      // Step 1, draw the background, if needed      int saveCount;      if (!dirtyOpaque) {          final Drawable background = mBGDrawable;          if (background != null) {              final int scrollX = mScrollX;              final int scrollY = mScrollY;              if (mBackgroundSizeChanged) {                  background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);                  mBackgroundSizeChanged = false;              }              if ((scrollX | scrollY) == 0) {                  background.draw(canvas);              } else {                  canvas.translate(scrollX, scrollY);                  background.draw(canvas);                  canvas.translate(-scrollX, -scrollY);              }          }      }      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);          // Step 6, draw decorations (scrollbars)          onDrawScrollBars(canvas);          // we're done...          return;      }  }  

6个步骤在开始部分已经介绍。

View的invalidate和postInvalidate方法源码分析

参考博客:http://blog.csdn.net/yanbober/article/details/46128379

参考:http://blog.csdn.net/guolin_blog/article/details/16330267

0 0
原创粉丝点击