invalidate和requestLayout之源碼區(qū)別

1. invalidate

/**
     * Indicates that this view was specifically invalidated, not just dirtied because some
     * child view was invalidated. The flag is used to determine when we need to recreate
     * a view's display list (as opposed to just returning a reference to its existing
     * display list).
     *
     * @hide
     */
    static final int PFLAG_INVALIDATED                 = 0x80000000;


    static final int PFLAG_DRAWING_CACHE_VALID         = 0x00008000;

看起來好像,子view invalidate就會導(dǎo)致父view PFLAG_INVALIDATED
但實際上,子view invalidate只會導(dǎo)致自身 PFLAG_INVALIDATED,父view的PFLAG_INVALIDATED不會被設(shè)置。
PFLAG_DRAWING_CACHE_VALID代表drawing_cache有效了,這個才是其中某個子view invalidate了,一個子view invalidate會導(dǎo)致父view的PFLAG_DRAWING_CACHE_VALID被置0,父view的父view也會PFLAG_DRAWING_CACHE_VALID被置為0,后文會分析

1.1 View#invalidateInternal

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

        // Reset content capture caches
        mCachedContentCaptureSession = null;

        //根據(jù)View的標(biāo)記位來判斷該子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;
            }

            //設(shè)置PFLAG_DIRTY標(biāo)記位
            mPrivateFlags |= PFLAG_DIRTY;

            //invalidateCache一般為true
            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            //把需要重繪的區(qū)域傳遞給父容器
            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);
                //調(diào)用父容器的方法,向上傳遞事件
                p.invalidateChild(this, damage);
            }

            // Damage the entire projection receiver, if necessary.
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }
        }
}

invalidate有多個重載方法,但最終都會調(diào)用invalidateInternal方法,在這個方法內(nèi)部,進(jìn)行了一系列的判斷,判斷View是否需要重繪,接著為該View設(shè)置標(biāo)記位,然后把需要重繪的區(qū)域傳遞給父容器,即調(diào)用父容器的invalidateChild方法。
注意其中:

if (invalidateCache) {
        mPrivateFlags |= PFLAG_INVALIDATED;
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}

2個標(biāo)志位PFLAG_INVALIDATEDPFLAG_DRAWING_CACHE_VALID,PFLAG_INVALIDATED置為1,PFLAG_DRAWING_CACHE_VALID置為0。

1.2 ViewGroup#invalidateChild

public final void invalidateChild(View child, final Rect dirty) {
        ...
        //設(shè)置 parent 等于自身
        ViewParent parent = this;
        if (attachInfo != null) {
            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;

            Matrix childMatrix = child.getMatrix();

            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.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        //對當(dāng)前View的標(biāo)記位進(jìn)行設(shè)置
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                    }
                }
                
                //調(diào)用ViewGroup的invalidateChildInParent
                //如果已經(jīng)達(dá)到最頂層view,則調(diào)用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) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

可以看到,在該方法內(nèi)部,先設(shè)置當(dāng)前視圖的標(biāo)記位,接著有一個do-while循環(huán),不停調(diào)用父viewinvalidateChildInParent,一直到調(diào)用ViewRootImplinvalidateChildInParent,作用就是不斷向上回溯父容器,求得父容器和子View需要重繪的區(qū)域的并集(dirty),當(dāng)父容器不是ViewRootImpl的時候,調(diào)用的是ViewGroup的invalidateChildInParent方法。
注意其中:

if (child.mLayerType != LAYER_TYPE_NONE) {
        mPrivateFlags |= PFLAG_INVALIDATED;
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}

PFLAG_DRAWING_CACHE_VALID置為0,在do-while循環(huán)后,當(dāng)前view的所有父view,父view的父view。。。都會被PFLAG_DRAWING_CACHE_VALID置為0。

1.3 ViewGroup # invalidateChildInParent

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                    != FLAG_OPTIMIZE_INVALIDATE) {
                //將dirty中的坐標(biāo)轉(zhuǎn)化為父容器中的坐標(biāo),考慮mScrollX和mScrollY的影響
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    //求并集,結(jié)果是把子視圖的dirty區(qū)域轉(zhuǎn)化為父容器的dirty區(qū)域
                    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();
                    }
                }

                //記錄當(dāng)前視圖的mLeft和mTop值,在下一次循環(huán)中會把當(dāng)前值再向父容器的坐標(biāo)轉(zhuǎn)化
                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;

                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            //返回當(dāng)前視圖的父容器
            return mParent;
        }

        return null;
    }

可以看出,調(diào)用invalidateChildInParent會傳進(jìn)去一個Rectdirty,代表子窗口需要刷新的Rect,調(diào)用offset方法,把當(dāng)前dirty區(qū)域的坐標(biāo)轉(zhuǎn)化為父容器中的坐標(biāo),然后父窗口會根據(jù)這個Rect和父窗口本身做并集union,從而得到父窗口需要刷新的Rect區(qū)域,然后再傳給父窗口的父窗口,一直遞歸直到ViewRootImpl
換句話說,dirty區(qū)域變成父容器區(qū)域。最后返回當(dāng)前視圖的父容器,以便進(jìn)行下一次循環(huán)。

1.4 ViewRootImpl # invalidateChildInParent

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "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);
            }
        }
        //這個方法會去調(diào)用scheduleTraversals方法
        invalidateRectOnScreen(dirty);

        return null;
    }

private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;

        // 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();
        }
    }

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

可以看出,該方法所做的工作與上面的差不多,都進(jìn)行了offsetunion對坐標(biāo)的調(diào)整,然后把dirty區(qū)域的信息保存在mDirty中,最后調(diào)用了invalidateRectOnScreen方法,這個方法會去調(diào)用scheduleTraversals方法,觸發(fā)View的工作流程,之前梳理過View的繪制流程,performTraversals方法中是有performMeasure,performLayout(),performDraw()三個繪制方法的。但是,這里由于沒有添加measurelayout的標(biāo)記位,因此measure、layout流程不會執(zhí)行,而是直接從draw流程開始。
稍微捋一下scheduleTraversals后的方法,

  1. mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 執(zhí)行 mTraversalRunnable這個Runnable
  2. 執(zhí)行run方法里的執(zhí)行doTraversal方法
  3. 執(zhí)行performTraversals
  4. 執(zhí)行performDraw
  5. 執(zhí)行draw
  6. 執(zhí)行mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this),mThreadedRenderer是一個ThreadedRenderer
  7. ThreadedRenderer類中的draw方法繼續(xù)調(diào)用updateRootDisplayList方法
  8. 執(zhí)行updateViewTreeDisplayList方法
  9. 執(zhí)行updateDisplayListIfDirty方法
  10. if 是父族view 或者是 invalidate的view本身,進(jìn)去調(diào)用dispatchGetDisplayList方法,反之進(jìn)else,設(shè)設(shè)標(biāo)識就完事了
private void updateViewTreeDisplayList(View view) {
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }

此時viewDecorViewDecorView會往下分發(fā)找到需要重繪的view,然后調(diào)用此view的draw方法。假設(shè)調(diào)用invalidate的view為a,a的parent為ap。我們知道此時a的PFLAG_DRAWING_CACHE_VALID 為0,PFLAG_INVALIDATED為1。而a的父族view,ap,app,appp等的PFLAG_DRAWING_CACHE_VALID為0,PFLAG_INVALIDATED為0.
所以上邊會把mRecreateDisplayList設(shè)置為false,后面會知道mRecreateDisplayList代表了哪個view要被重繪的標(biāo)記(在ViewupdateDisplayListIfDirty方法中就會把它置為true),只有invalidate的view a的mRecreateDisplayListtrue,其他都是false。

再來看DecorView如何分發(fā)的。DecorViewupdateViewTreeDisplayList會調(diào)updateDisplayListIfDirty,如下,因為DecorView是a的父族view,所以會進(jìn)@AAA的if,然后mRecreateDisplayListfalse,所以會進(jìn)@BBB調(diào)用 dispatchGetDisplayList,而invalidate的view a卻進(jìn)不去@BBB,因為它的mRecreateDisplayList是true,所以它會走到下面的try方法體里,進(jìn)行重繪。

View # updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        if (!canHaveDisplayList()) {
            // can't populate RenderNode, don't try
            return renderNode;
        }

        //@AAA
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.hasDisplayList()
                || (mRecreateDisplayList)) {
            // Don't need to recreate the display list, just need to tell our
            // children to restore/recreate theirs
           //@BBB
            if (renderNode.hasDisplayList()
                    && !mRecreateDisplayList) {
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchGetDisplayList();

                return renderNode; // no work needed
            }

            // If we got here, we're recreating it. Mark it as such to ensure that
            // we copy in child display lists into ours in drawChild()
            mRecreateDisplayList = true;

            int width = mRight - mLeft;
            int height = mBottom - mTop;
            int layerType = getLayerType();

            final RecordingCanvas canvas = renderNode.beginRecording(width, height);

            try {
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    computeScroll();

                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        //@CCC
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (debugDraw()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        draw(canvas);
                    }
                }
            } finally {
                renderNode.endRecording();
                setDisplayListProperties(renderNode);
            }
        } else {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        return renderNode;
    }

ViewGroup # dispatchGetDisplayList
protected void dispatchGetDisplayList() {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                recreateChildDisplayList(child);
            }
        }
        if (mOverlay != null) {
            View overlayView = mOverlay.getOverlayView();
            recreateChildDisplayList(overlayView);
        }
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size();
            for (int i = 0; i < disappearingCount; ++i) {
                final View child = disappearingChildren.get(i);
                recreateChildDisplayList(child);
            }
        }
    }

private void recreateChildDisplayList(View child) {
        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
        child.updateDisplayListIfDirty();
        child.mRecreateDisplayList = false;
    }

dispatchGetDisplayList的作用主要就是一個for循環(huán),內(nèi)調(diào)用recreateChildDisplayList,讓子view recreate & display。

總結(jié)

parent: updateDisplayListIfDirty -> dispatchGetDisplayList -> for() recreateChildDisplayList
child: recreateChildDisplayList -> updateDisplayListIfDirty
我們在具體分析下各個view會如何表現(xiàn),核心代碼是updateDisplayListIfDirty,

父族view的執(zhí)行流程是
parent: updateDisplayListIfDirty -> dispatchGetDisplayList -> for() recreateChildDisplayList
child: recreateChildDisplayList -> updateDisplayListIfDirty

如果是a本身,那么@AAA進(jìn)去,但是由于a的mRecreateDisplayList為true,所以進(jìn)不去@BBB會到下面的try方法塊進(jìn)行重繪,這就是invalidate導(dǎo)致的分發(fā)過程

如果不是父族view也不是a本身,那么PFLAG_DRAWING_CACHE_VALID為1,會走@AAA的else設(shè)置標(biāo)志位,然后結(jié)束,不會分發(fā)到子view。
所以整個過程中只有a調(diào)用了draw方法進(jìn)行重繪,這是PFLAG_INVALIDATED標(biāo)志位起的作用

ViewGroup的invalidate

我們再來看看ViewGroup的invalidate,剛才說了invalidate如果是個view,那就只有自己本身會draw,如果是ViewGroup呢?
因為一般的ViewGroup都是SKIP_DRAW的,所以會走到@CCCdispatchDraw,dispatchDraw的實現(xiàn)一般在ViewGroup里,就是**調(diào)用子view的draw(注意是調(diào)用的3參的draw方法,而不是單參的方法)** 所以一般來說ViewGroupinvalidate`就是對子view進(jìn)行重繪。(android.view.View#draw(android.graphics.Canvas, android.view.ViewGroup, long))

用人話說:當(dāng)子View調(diào)用了invalidate方法后,會為該View添加一個標(biāo)記位,同時不斷向父容器請求刷新,父容器通過計算得出自身需要重繪的區(qū)域,直到傳遞到ViewRootImpl中,最終觸發(fā)performTraversals方法,進(jìn)行開始View樹重繪流程(只繪制需要重繪的視圖)。

  1. view的invalidate并不會調(diào)用ViewRootImplinvalidate。
  2. performDraw的過程中,大部分view的updateDisplayListIfDirty都會被調(diào)用,但是只有設(shè)了標(biāo)志位的view會調(diào)用draw方法進(jìn)而調(diào)用onDraw

PostInvalidate

這個方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同,postInvalidate是在非UI線程中調(diào)用,invalidate則是在UI線程中調(diào)用。

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的時候才會繼續(xù)執(zhí)行,即只有確保視圖被添加到窗口的時候才會通知view樹重繪,因為這是一個異步方法,如果在視圖還未被添加到窗口就通知重繪的話會出現(xiàn)錯誤,所以這樣要做一下判斷。接著調(diào)用了ViewRootImpl#dispatchInvalidateDelayed方法:

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

這里用了Handler,發(fā)送了一個異步消息到主線程,顯然這里發(fā)送的是MSG_INVALIDATE,即通知主線程刷新視圖,具體的實現(xiàn)邏輯我們可以看看該mHandler的實現(xiàn):

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

可以看出,參數(shù)message傳遞過來的正是View視圖的實例,然后直接調(diào)用了invalidate方法,然后繼續(xù)invalidate流程。

requestLayout

從方法名字可以知道,“請求布局”,那就是說,如果調(diào)用了這個方法,那么對于一個子View來說,應(yīng)該會重新進(jìn)行布局流程。但是,真實情況略有不同,如果子View調(diào)用了這個方法,其實會從View樹重新進(jìn)行一次測量、布局、繪制這三個流程,最終就會顯示子View的最終情況。那么,這個方法是怎么實現(xiàn)的呢?我們從源碼角度進(jìn)行解析。

View # requestLayout
//從源碼注釋可以看出,如果當(dāng)前View在請求布局的時候,View樹正在進(jìn)行布局流程的話,
//該請求會延遲到布局流程完成后或者繪制流程完成且下一次布局發(fā)現(xiàn)的時候再執(zhí)行。
@CallSuper
public 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;
    }

    //為當(dāng)前view設(shè)置標(biāo)記位 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方法中,首先先判斷當(dāng)前View樹是否正在布局流程,接著為當(dāng)前子View設(shè)置標(biāo)記位,該標(biāo)記位的作用就是標(biāo)記了當(dāng)前的View是需要進(jìn)行重新布局的,接著調(diào)用mParent.requestLayout方法,這個十分重要,因為這里是向父容器請求布局,即調(diào)用父容器的requestLayout方法,為父容器添加PFLAG_FORCE_LAYOUT標(biāo)記位,而父容器又會調(diào)用它的父容器的requestLayout方法,即requestLayout事件層層向上傳遞,直到DecorView,即根View,而根View又會傳遞給ViewRootImpl,也即是說子View的requestLayout事件,最終會被ViewRootImpl接收并得到處理??v觀這個向上傳遞的流程,其實是采用了責(zé)任鏈模式,即不斷向上傳遞該事件,直到找到能處理該事件的上級,在這里,只有ViewRootImpl能夠處理requestLayout事件。

ViewRootImpl # requestLayout:
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

我們可以簡單的認(rèn)為mLayoutRequestedtrue會觸發(fā)performMeasure(內(nèi)部會調(diào)用onMeasure)和performLayout(內(nèi)部會調(diào)用onLayout)。然后在performDraw內(nèi)部draw的過程中發(fā)現(xiàn)mDirty為空,所以onDraw不會被調(diào)用,不重繪。
這么看來requestLayout不會導(dǎo)致onDraw調(diào)用了?
也不見得,我們知道requestLayout會導(dǎo)致performMeasureperformLayout,如果在layout過程中發(fā)現(xiàn)l,t,r,b和以前不一樣,那就會觸發(fā)一次invalidate。代碼在ViewsetFrame中,這個會在layout時被調(diào)用。

protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
            。。。
    }        

所以requestLayout有可能會導(dǎo)致onDraw被調(diào)用,也可能不導(dǎo)致onDraw被調(diào)用,取決于view的l,t,r,b是否改變。

一些知識點(diǎn):

  1. view不停找parent可以一直找到DecorView,按理說DecorView是頂點(diǎn)了,但是DecorView還有個虛擬父view:ViewRootImpl。 ViewRootImpl不是一個View或者ViewGroup,他有個成員mView是DecorView,所有的操作從ViewRootImpl開始自上而下分發(fā)
  2. view的invalidate不會導(dǎo)致ViewRootImplinvalidate被調(diào)用,而是遞歸調(diào)用父view的invalidateChildInParent,直到ViewRootImplinvalidateChildInParent,然后觸發(fā)peformTraversals,會導(dǎo)致當(dāng)前view被重繪,由于mLayoutRequestedfalse,不會導(dǎo)致onMeasureonLayout被調(diào)用,而OnDraw會被調(diào)用
  3. 一個view的invalidate會導(dǎo)致本身PFLAG_INVALIDATED1,導(dǎo)致本身以及父族viewgroup的PFLAG_DRAWING_CACHE_VALID0
  4. requestLayout會直接遞歸調(diào)用父窗口的requestLayout,直到ViewRootImpl,然后觸發(fā)peformTraversals,由于mLayoutRequestedtrue,會導(dǎo)致onMeasureonLayout被調(diào)用。不一定會觸發(fā)onDraw
  5. requestLayout觸發(fā)onDraw可能是因為在在layout過程中發(fā)現(xiàn)l,t,r,b和以前不一樣,那就會觸發(fā)一次invalidate,所以觸發(fā)了onDraw,也可能是因為別的原因?qū)е?code>mDirty非空(比如在跑動畫)
  6. requestLayout會導(dǎo)致自己以及父族view的PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED標(biāo)志被設(shè)置。
    7.一般來說,只要刷新的時候就調(diào)用invalidate,需要重新measure就調(diào)用requestLayout,后面再跟個invalidate(為了保證重繪),個人理解。

感謝:
https://blog.csdn.net/litefish/article/details/52859300

https://www.cnblogs.com/ldq2016/p/9035332.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容