View的绘制流程源码分析(Android开发艺术探索学习笔记)
来源:互联网 发布:linux snmp 监控软件 编辑:程序博客网 时间:2024/05/21 17:02
概述
我之前的一篇文章Activity的Window创建及DecorView的添加(Android开发艺术探索学习笔记)说到View的绘制流程就是从ViewRootImpl的performTraversals()开始。那究竟是怎么调用performTraversals()的呢。
上次说到的WindowManagerGlobal的addView():
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); …… root.setView(view, wparams, panelParentView); …… }
其中调用了View的setLayoutParams()和ViewRootImpl的setView(),这两个方法又都调用了requestLayout():
/** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view * tree. This should not be called while the view hierarchy is currently in a layout * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the * end of the current layout pass (and then layout will run again) or after the current * frame is drawn and the next layout occurs. * * <p>Subclasses which override this method should call the superclass method to * handle possible request-during-layout errors correctly.</p> *///从源码注释可以看出,如果当前View在请求布局的时候,View树正在进行布局流程的话,//该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行。@CallSuperpublic void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } //为当前view设置标记位 PFLAG_FORCE_LAYOUT mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { //向父容器请求布局 mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; }}
在requestLayout方法中,首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位,该标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理requestLayout事件。在ViewRootImpl中,重写了requestLayout方法,我们看看这个方法:
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
其中调用了scheduleTraversals():
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
我们可以看到在scheduleTraversals中会调用Choregrapher.postCallback,将一个TraversalRunnable对象post到消息队列中。
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
发现TraversalRunnable 对象被调用时会执行run()中的doTraversal():
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
其中调用了我们之前所说的performTraversals()。
performTraversals()中会按顺序调用performMeasure(),performLayout(),performDraw()。接着performMeasure()调用measure(),measure()调用onMeasure();performLayout()调用layout(),layout()调用onLayout();performDraw()调用draw(),draw()调用onDraw()。
以下就是View的绘制流程图:
measure()
在讨论measure()之前,先探究一下MeasureSpec。
MeasureSpec 封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:
UNSPECIFIED
父容器不对 view 有任何限制,要多大给多大。EXACTLY
父容器已经检测出 view 所需要的大小。AT_MOST
父容器指定了一个大小, view 的大小不能大于这个值。
MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值可以避免过多的对象内存分配,为了方便操作,其提供了打包 / 解包方法。
MeasureSpecs 代表一个 32 位 int 值,高 2 位代表 SpecMode ,低 30 位代表 SpecSize。
下面先看一下MeasureSpec 内部的一些常量的定义,通过下面的代码,应该不难理解MeasureSpec的工作原理:
private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
MeasureSpec 与 LayoutParams的关系:
对于 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的LayoutParams 来共同确定。
对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定。
MeasureSpec 一旦确定后, onMeasure 中就可以确定自身的宽高。
以下代码描述了DecorView的MeasureSpec的产生过程:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
顶级容器DecorView的MeasureSpec的产生过程就很明确了,具体来说其遵守如下规则:
LayoutParams.MATCH_PARENT:其模式为精确模式,大小就是窗口的大小。
LayoutParams.WRAP_CONTENT:其模式为最大模式,大小不定,但是不能超过窗口的大小。
固定大小(比如100dp):其模式为精确模式,大小为LayoutParams中指定的大小。
关于应用层View,继续看ViewGroup中的getChildMeasureSpec():
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) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 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; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } 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; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
前面已经提到,对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定,那么针对不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。
这里简单说下,当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小。
当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间。
当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。
可能大家会发现,在我们的分析中漏掉了Unspecified模式,这个模式主要用于系统内部多次measure的情况下,一般来说,我们不需要关注此模式。
下面正式开始分析View的绘制流程。
1.measure(int widthMeasureSpec, int heightMeasureSpec):
该方法定义在View.java类中,为 final 类型,不可被复写,但 measure 调用链最终会回调View/ViewGroup 对象的 onMeasure()方法,因此自定义视图时,只需要复写 onMeasure() 方法即可。
2.onMeasure(int widthMeasureSpec, int heightMeasureSpec):
该方法就是我们自定义视图中实现测量逻辑的方法,该方法的参数是父视图对子视图的 width 和 height 的测量要求。在我们自身的自定义视图中,要做的就是根据该 widthMeasureSpec 和 heightMeasureSpec 计算视图的 width 和 height,不同的模式处理方式不同。
3.setMeasuredDimension():
是测量阶段终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中调用,将计算得到的尺寸,传递给该方法,测量阶段即结束。该方法也是必须要调用的方法,否则会报异常。在我们在自定义视图的时候,不需要关心系统复杂的 Measure 过程的,只需调用setMeasuredDimension()设置根据 MeasureSpec 计算得到的尺寸即可,你可以参考ViewPagerIndicator 的 onMeasure 方法。
下面我们取 ViewGroup 的 measureChildren(int widthMeasureSpec, int heightMeasureSpec) 方法对复合 View 的 Measure 流程做一个分析:
/** * 请求所有子 View 去 measure 自己,要考虑的部分有对子 View 的测绘要求 MeasureSpec 以及其自身的 padding * 这里跳过所有为 GONE 状态的子 View,最繁重的工作是在 getChildMeasureSpec 方法中处理的 * * @param widthMeasureSpec 对该 View 的 width 测绘要求 * @param heightMeasureSpec 对该 View 的 height 测绘要求 */ 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();//获取 Child 的 LayoutParams final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec 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); } /** * * 用于获取 View 最终的大小,父视图提供了宽、高的约束信息 * 一个 View 的真正的测量工作是在 onMeasure(int, int) 中,由该方法调用。 * 因此,只有 onMeasure(int, int) 可以而且必须被子类复写 * * @param widthMeasureSpec 在水平方向上,父视图指定的的 Measure 要求 * @param heightMeasureSpec 在竖直方向上,控件上父视图指定的 Measure 要求 * */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
MeasureChildren 的方法调用流程图:
layout
layout():
public void layout(int l, int t, int r, int b) { ... //判断标记位是否为PFLAG_LAYOUT_REQUIRED,如果有,则对该View进行布局 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); //onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED标记位 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); } } } //最后清除PFLAG_FORCE_LAYOUT标记位 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}
首先要明确的是,子视图的具体位置都是相对于父视图而言的。View 的 onLayout 方法为空实现,而 ViewGroup 的 onLayout 为 abstract 的,因此,如果自定义的 View 要继承 ViewGroup 时,必须实现 onLayout 函数。
在 onLayout 过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight,作为自己的 width 和 height。然后调用每一个子视图的layout(l, t, r, b)函数,来确定每个子视图在父视图中的位置。
@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); } } /** * 遍历所有的子 View,为其设置相对父视图的坐标 */ 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) {//不需要立即展示的 View 设置为 GONE 可加快绘制 final int childWidth = child.getMeasuredWidth();//measure 过程确定的 Width final int childHeight = child.getMeasuredHeight();//measure 过程确定的 height ...确定 childLeft、childTop 的值 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) } /** * 为该子 View 设置相对其父视图上的坐标 */ protected boolean setFrame(int left, int top, int right, int bottom) { ... }
draw()
1.draw(Canvas canvas):
由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
/** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. * * @param canvas The Canvas to which the View is rendered. * * 根据给定的 Canvas 自动渲染 View(包括其所有子 View)。在调用该方法之前必须要完成 layout。当你自定义 view 的时候, * 应该去是实现 onDraw(Canvas) 方法,而不是 draw(canvas) 方法。如果你确实需要复写该方法,请记得先调用父类的方法。 */ 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) */ // 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); }
由上面的处理过程,我们也可以得出一些优化的小技巧:当不需要绘制Layer 的时候第二步和第五步会跳过。因此在绘制的时候,能省的 layer 尽可省,可以提高绘制效率。
2.onDraw():
View 的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。
3.dispatchDraw() :
发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()进行复写。
dispatchDraw(Canvas canvas){... if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//处理 ChildView 的动画 final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {//只绘制 Visible 状态的布局,因此可以通过延时加载来提高效率 final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount);// 添加布局变化的动画 bindLayoutAnimation(child);//为 Child 绑定动画 if (cache) { child.setDrawingCacheEnabled(true); if (buildCache) { child.buildDrawingCache(true); } } } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start();// 启动 View 的动画} // 绘制 ChildView for (int i = 0; i < childrenCount; i++) { int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } }...}protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime);}/** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * This draw() method is an implementation detail and is not intended to be overridden or * to be called from anywhere else other than ViewGroup.drawChild(). */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { ... }
invalidate
我们一般还会用到invalidate方法,该方法的调用会引起View树的重绘,常用于内部调用(比如setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法,那么我们来分析一下它的实现。
public void invalidate() { invalidate(true);}void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } //这里判断该子View是否可见或者是否处于动画中 if (skipInvalidate()) { return; } //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } //设置PFLAG_DIRTY标记位 mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Propagate the damage rectangle to the parent view. //把需要重绘的区域传递给父容器 final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); //调用父容器的方法,向上传递事件 p.invalidateChild(this, damage); } ... }}
可以看出,invalidate有多个重载方法,但最终都会调用invalidateInternal方法,在这个方法内部,进行了一系列的判断,判断View是否需要重绘,接着为该View设置标记位,然后把需要重绘的区域传递给父容器,即调用父容器的invalidateChild方法。
接着我们看ViewGroup的invalidateChild:
/** * Don't call or override this method. It is used for the implementation of * the view hierarchy. */public final void invalidateChild(View child, final Rect dirty) { //设置 parent 等于自身 ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes // through final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION; // Check whether the child that requests the invalidate is fully opaque // Views being animated or transformed are not considered opaque because we may // be invalidating their old position and need the parent to paint behind them. Matrix childMatrix = child.getMatrix(); final boolean isOpaque = child.isOpaque() && !drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); // Mark the child as dirty, using the appropriate flag // Make sure we do not set both flags at the same time int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY; if (child.mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //储存子View的mLeft和mTop值 final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; ... do { View view = null; if (parent instanceof View) { view = (View) parent; } if (drawAnimation) { if (view != null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } } // If the parent is dirty opaque or not dirty, mark it dirty with the opaque // flag coming from the child that initiated the invalidate if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { //对当前View的标记位进行设置 view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } //调用ViewGrup的invalidateChildInParent,如果已经达到最顶层view,则调用ViewRootImpl //的invalidateChildInParent。 parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) (boundingRect.left - 0.5f), (int) (boundingRect.top - 0.5f), (int) (boundingRect.right + 0.5f), (int) (boundingRect.bottom + 0.5f)); } } } while (parent != null); }}
可以看到,在该方法内部,先设置当前视图的标记位,接着有一个do…while…循环,该循环的作用主要是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty)。当父容器不是ViewRootImpl的时候,调用的是ViewGroup的invalidateChildInParent方法,我们来看看这个方法,ViewGroup的invalidateChildInParent:
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { //将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { //求并集,结果是把子视图的dirty区域转化为父容器的dirty区域 dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //记录当前视图的mLeft和mTop值,在下一次循环中会把当前值再向父容器的坐标转化 location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } //返回当前视图的父容器 return mParent; } ... } return null;}
可以看出,这个方法做的工作主要有:调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标,接着调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环。
回到上面所说的do…while…循环,由于不断向上调用父容器的方法,到最后会调用到ViewRootImpl的invalidateChildInParent方法,我们来看看它的源码,ViewRootImpl的invalidateChildInParent:
@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty(); } if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } return null;}
可以看出,该方法所做的工作与上面的差不多,都进行了offset和union对坐标的调整,然后把dirty区域的信息保存在mDirty中,最后调用了scheduleTraversals方法,触发View的工作流程,由于没有添加measure和layout的标记位,因此measure、layout流程不会执行,而是直接从draw流程开始。
好了,现在总结一下invalidate方法,当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
postInvalidate
这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。
接下来我们分析postInvalidate方法的原理,首先看View的postInvalidate:
public void postInvalidate() { postInvalidateDelayed(0);}public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); }}
由以上代码可以看出,只有attachInfo不为null的时候才会继续执行,即只有确保视图被添加到窗口的时候才会通知view树重绘,因为这是一个异步方法,如果在视图还未被添加到窗口就通知重绘的话会出现错误,所以这样要做一下判断。接着调用了ViewRootImpl#dispatchInvalidateDelayed方法:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds);}
这里用了Handler,发送了一个异步消息到主线程,显然这里发送的是MSG_INVALIDATE,即通知主线程刷新视图,具体的实现逻辑我们可以看看该mHandler的实现:
final ViewRootHandler mHandler = new ViewRootHandler();final class ViewRootHandler extends Handler { @Override public String getMessageName(Message message) { .... } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ... } }}
可以看出,参数message传递过来的正是View视图的实例,然后直接调用了invalidate方法,然后继续invalidate流程。
一般来说,如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。
而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效。
参考:
1.公共技术点之 View 绘制流程
2.Android View 深度分析requestLayout、invalidate与postInvalidate
3.Android View系统解析(下)
- View的绘制流程源码分析(Android开发艺术探索学习笔记)
- Android开发艺术探索笔记(8)- View的基础
- Android开发艺术探索笔记(9)- View的滑动
- 《Android开发艺术探索》笔记(View的知识体系)
- Android开发艺术探索学习笔记(3)--View的事件体系(1)
- 《Android开发艺术探索》之学习笔记(四)View的工作原理
- 《Android开发艺术探索》之学习笔记(三)View的基础知识
- View的事件分发机制(Android开发艺术探索学习笔记)
- 在Activity中获取View的高度(Android开发艺术探索学习笔记)
- 《Android开发艺术探索》读书笔记——Toast调用流程的源码分析
- android 艺术开发探索 view移动 笔记
- View的滑动冲突解决-android开发艺术探索笔记
- Android 开发艺术探索笔记-View的事件分发
- Android开发艺术探索学习-View的基本知识
- Android开发艺术探索学习-View的滑动
- Android开发艺术探索笔记(15)- 自定义View
- 《Android开发艺术探索》之View的事件体系和工作原理学习笔记
- Android开发艺术探索学习笔记3——View的事件体系
- datastage序列job参数传入内部嵌套的job注意事项
- 如何解决web系统session劫持
- CSS的规范化
- 2017年华东师范大学网络赛 C
- Struts2之OGNL表达式与值栈对象及常用标签的使用
- View的绘制流程源码分析(Android开发艺术探索学习笔记)
- 单位阶跃函数(Heaviside/unit step function)—— 化简分段函数
- 引导页的实现
- 函数
- js中的Math和Date对象
- Ganglia与Nagios介绍
- Fedora 安装ruby
- 市场细分时代,PC应如何利用好用户思维?
- ubuntu下SSH的安装、使用与配置root用户支持SSH