当前位置: 代码迷 >> Android >> Android requestLayout 跟 invalidate
  详细解决方案

Android requestLayout 跟 invalidate

热度:579   发布时间:2016-04-27 23:53:24.0
Android requestLayout 和 invalidate

这两个方法很多人 搞不太清楚,这里小结一下:

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

至此,上面的流程图我们得到验证。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案