内容简介:view的绘制是从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个 View 控制负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。视图操作的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)。performTraversals 方法在viewRoot的实现类 ViewRootImpl 里面:view绘制流程图:看看performTraversals方法源码:
view的绘制是从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个 View 控制负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。视图操作的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)。performTraversals 方法在viewRoot的实现类 ViewRootImpl 里面:
- measure方法用于测量View的宽高
- layout方法用于确定View在父容器中的位置
- draw方法负责将View绘制在屏幕上
view绘制流程图:
看看performTraversals方法源码:
private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... // 测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 布局 performLayout(lp, mWidth, mHeight); ... // 绘制 performDraw(); ... } 复制代码
依次调用performMeasure、performLayout、performDraw方法,分别完成顶级View的measure、layout、draw流程。performMeasure会调用measure方法,而measure又会调用onMeasure方法,在onMeasure方法中又会对子元素进行measure,这样重复下去就完成了整个View树的遍历。
performLayout、performDraw传递过程也非常类似,不过performDraw是在draw方法中通过dispatchDraw方法实现的。
measure过程决定了View的宽高,而Layout方法则确定了四个顶点的坐标和实际的宽高(往往等于measure中计算的宽高),draw方法则决定了View的显示。只有完成了draw方法才能正确显示在屏幕上。
2. MeasureSpec
MeasureSpec是measure的重要参数。 MeasureSpec 表示的是一个 32 位的整数值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize(PS:这里用到了位运算进行状态压缩来节省内存)。MeasureSpec 是 View 类的一个静态内部类,用来说明应该如何测量这个View,有三种模式:
-
UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
-
EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
-
AT_MOST:最大值模式,当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
下表是普通View的MeasureSpec的创建规则对应表:
childLayoutParams/parentSpecParams | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY childSIze | EXACTLY childSIze | EXACTLY childSIze |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
wrap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
3. Measure
View的绘制从测量开始,看看performMeasure()方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } 复制代码
具体操作是分发给 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中传递给子 View。ViewGroup 通过遍历自身所有的子 View,并逐个调用子 View 的 measure 方法实现测量操作。
3.1 View的measure过程
View的measure过程由measure方法来完成,measure方法是一个final方法,不能重写,它会调用VIew的onMeasure方法。onMeasure方法中会调用getDefaultSize方法,而getDefault方法中又会调用getSuggestedWidth和getSuggestedHeight方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } 复制代码
/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */ 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; } 复制代码
getDefaultSize方法所返回的就是测量后的View的大小。
接着看getSuggestedWidth和getSuggestedHeight方法:
/** * Returns the suggested minimum width that the view should use. This * returns the maximum of the view's minimum width * and the background's minimum width * ({@link android.graphics.drawable.Drawable#getMinimumWidth()}). * <p> * When being used in {@link #onMeasure(int, int)}, the caller should still * ensure the returned width is within the requirements of the parent. * * @return The suggested minimum width of the view. */ protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } 复制代码
它在没有指定background的情况下,返回的是minSize这一属性对应的值,而在指定了背景的情况下,返回的是背景drawable的getMinimumWidth / getMinimumHeight方法对应的值
这两个方法在Drawable有原始宽度的情况下返回原始宽度,否则返回0
从getDefaultSize方法可以看出,View的宽高由specSize决定。
3.2 ViewGroup的measure过程
ViewGroup除了完成自己的measure过程,还会遍历调用子元素的measure方法,然后子元素再次递归执行,ViewGroup是一个抽象类,因此没有重写View的onMeasure方法。但它提供了一个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); } 复制代码
可以看到,ViewGroup执行measure时,会遍历子元素,调用measureChild方法对子元素进行measure。
在measureChild方法中,取出子元素的LayoutParams,通过getChildMeasureSpec方法创建子元素MeasureSpec,然后传递给View的measure方法进行测量。
ViewGroup没有定义测量具体过程,因为它是个抽象类。具体的测量过程的onMeasure方法需要子类来实现,由于它的子类的特性可能会很大不同,所以没法做统一处理(如LinearLayout和RelativeLayout)。
4. Layout
Layout流程的作用是ViewGroup确定子元素的位置。当ViewGroup被确定后,在onLayout中会遍历所有子元素并调用layout方法,在layout方法中会调用onLayout方法。layout方法确定View的位置,而onLayout方法则确定所有子元素的位置。
ViewRootImpl 的 performLayout 如下:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; } 复制代码
先看View的layout方法:
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } 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); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = 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); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { // We have a robust focus, so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) { // This is a weird case. Most-likely the user, rather than ViewRootImpl, called // layout. In this case, there's no guarantee that parent layouts will be evaluated // and thus the safest action is to clear focus here. clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (!hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } // otherwise, we let parents handle re-assigning focus during their layout passes. } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != null) { // Try to restore focus as close as possible to our starting focus. if (!restoreDefaultFocus() && !hasParentWantsFocus()) { // Give up and clear focus once we've reached the top-most parent which wants // focus. focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } } 复制代码
首先,通过setFrame方法设定View四个顶点的位置(初始化mLeft,mRight,mTop,mBottom)。四个顶点一旦确定,则在父容器中的位置也确定了,接着便会调用onLayout方法,来让父容器确定子容器的位置。onLayout同样和具体布局有关,因此View和ViewGroup均没有实现onLayout方法。
5. Draw
draw流程是将View绘制到屏幕上。先看看performDraw 方法:
private void performDraw() { .... try { boolean canUseAsync = draw(fullRedrawNeeded); .... } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... } 复制代码
private boolean draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... mView.draw(canvas); ... } 复制代码
最终调用到每个 View 的 draw 方法绘制每个具体的 View,绘制基本上可以分为六个步骤:
public void draw(Canvas canvas) { ... // Step 1, draw the background, if needed if (!dirtyOpaque) { drawBackground(canvas); } ... // Step 2, save the canvas' layers saveCount = canvas.getSaveCount(); ... // 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 canvas.drawRect(left, top, right, top + length, p); ... canvas.restoreToCount(saveCount); ... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); } 复制代码
View绘制过程的传递是通过dispatchDraw实现的。dispatchDraw会遍历调用所有子元素的draw方法。这样draw事件就一层层传递了下来。
它有个比较特殊的setWillNotDraw方法。如果一个View不需要绘制任何内容,在我们设定这个标记为true后,系统就会对其进行相应优化。一般View没有启用这个标记位。但ViewGroup是默认启用的。
它对实际开发的意义在于:我们的自定义控件继承于ViewGroup并且不具备绘制功能时,可以开启这个标记位方便系统进行后续优化。
6. 结束语
关于view绘制的原理在网上也特别多,时间久了也容易忘记,看一遍别人的,还不如顺便把他们的记录下来,方便自己以后温习。
重要参考:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
基于MVC的JavaScript Web富应用开发
麦卡劳(Alex MacCaw) / 李晶、张散集 / 电子工业出版社 / 2012-5 / 59.00元
《JavaScript Web 富应用开发》Developing JavaScript Web Applications是 Alex MacCaw 的新作(由O'Reilly出版发行),本书系统而深入的讲解了如何使用最前沿的Web技术构建下一代互联网富应用程序。作者 Alex MacCaw 是一名Ruby/JavaScript 程序员,在开源社区中很有名望,是Spine框架的作者,同时活跃在纽约、......一起来看看 《基于MVC的JavaScript Web富应用开发》 这本书的介绍吧!