这两个方法很多人 搞不太清楚,这里小结一下:
View的流程图
对于标题提及的两个方法 调用invalidate()或者requestLayout()会触发哪些方法,一图道破天机。
源代码
现在来看看具体的代码: android.view.ViewRootImpl
@Overridepublic void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); }}
这两个方法不约而同的调用了scheduleTraversals(),区别就是requsetLayout检查当前是否在主线程中,并且置位mLayoutRequsted = true,而invalidate方法没有检查。
接着我们进一步追踪下去看看scheduleTraversals()
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); }}
这里会post一个runnable请求就是mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); }}
进入doTraversal()方法:
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } }}
注意这个方法中,我们重点关注performTraversals()方法:
这个方法是ViewRootImpl最复杂的方法,将近800行代码Android View 分析(下)
这篇博文表粗略的分析了这个方法,这里不打算详细分析这个方法,我们仅仅做粗线条的了解, 进一步验证博文最上面的图是否正确:
ViewRootImpl这个类的L1767行:
if (!mStopped) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() + " coveredInsetsChanged=" + contentInsetsChanged); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if // needs be int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false; if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { if (DEBUG_LAYOUT) Log.v(TAG, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; } }}
只要没有stop 并且
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged)
这个条件成立:
就会引发 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
经过Debug可知:
requsetLayout触发performTraversal方法,进而调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),进一步的调用performLayout方法,此时layoutRequested = true
invalidate触发performTraversal方法,因为上面的条件不成立,所以不会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),同样也不会调用performLayout方法。
final boolean didLayout = layoutRequested && !mStopped;boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes;if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); ...}
所以到这里可以确认的是:
**requsetLayout方法会调用performMeasure和performLayout方法
而invalidate方法则一个都不会调用。**
我们接着往下看是否会接着调用performDraw方法
if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); }}
由上面的代码可知:需满足以下几个条件才会触发performDraw方法
if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) }
一般情况下 以上条件都会满足,除非设置了明显的标志位,所以performDraw方法会执行。
我们来大概看一下上面的几个标志位:
cancelDraw 默认是false
skipDraw 默认是false
newSurface 位于:
if (!hadSurface) { if (mSurface.isValid()) { // If we are creating a new surface, then we need to // completely redraw it. Also, when we get to the // point of drawing it we will hold off and schedule // a new traversal instead. This is so we can tell the // window manager about all of the windows being displayed // before actually drawing them, so it can display then // all at once. newSurface = true; mFullRedrawNeeded = true; mPreviousTransparentRegion.setEmpty(); if (mAttachInfo.mHardwareRenderer != null) { try { hwInitialized = mAttachInfo.mHardwareRenderer.initialize( mSurface); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } } }}
首先最开始的判断分支不会成立,因为 hadSurface为true,所以newSurface = false
最后一个标志位mReportNextDraw:
// Remember if we must report the next draw.if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mReportNextDraw = true;}
这里在执行判断该标志位之前就置位为true。所以一般会进入performDraw方法,
我们小结一下:
requsetLayout方法会触发performMeasure和performLayout,而performDraw方法是否触发视情况而定
如果以下条件满足
if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) }
则触发performDraw方法,反之,不会触发performDraw方法。
而invalidate方法则只会触发performDraw方法,因为一般情况下条件都会满足:
if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) }
至此,上面的流程图我们得到验证。
版权声明:本文为博主原创文章,未经博主允许不得转载。