1. 程式人生 > >安卓中invalidate和requestLayout的實現和區別

安卓中invalidate和requestLayout的實現和區別

安卓中invalidate和requestLaoyout都是重新整理View用的,用下面這張圖來表示他們的流程

這裡寫圖片描述

invalidate和postInvalidate的實現

兩者的區別:invalidate是在主執行緒呼叫,postInvalidate在非主執行緒中呼叫。
首先看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); } }

會呼叫到ViewRootImpl的dispatchInvalidateDelayed方法

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

就是用handler在非主執行緒傳送了一個非同步訊息,接著來看Handler是怎麼處理這個訊息的

public void handleMessage(Message msg) {
            switch
(msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break;

其中((View) msg.obj)就是最先呼叫postInvalidate的控制元件,也就是現在view在主執行緒實現了呼叫invalidate方法。
我們接著看invalidate方法

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

主要看p.invalidateChild(this, damage)這個方法,其中p為ViewParent,可以理解為他的父佈局,我們現在看看ViewGroup中的這個方法

public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
        ..........
        ..........
        ..........
            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.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }

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

可以看到這個方法會進入一個do while迴圈,不停地呼叫其ViewParent的invalidateChildInParent方法,直到返回值為空時才結束這個方法。最終的
ViewParent為ViewRootImpl類,他的這個方法實現為

    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        ..........
        ..........
        ..........
        invalidateRectOnScreen(dirty);
        return null;
    }

private void invalidateRectOnScreen(Rect dirty) {
        ..........
        ..........
        ..........
        scheduleTraversals();
    }

首先確定他的返回值為null,可知前面的迴圈會結束,而最終會呼叫到scheduleTraversals方法中,這個方法會通過Handler的Runnable傳送一個非同步訊息,最終會呼叫ViewRootImpl的performTraversals()方法執行重繪。

requestLayout的實現

requestLayout的實現相比於invalidate要簡單點

public void requestLayout() {
        ..........
        ..........
        ..........
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        ..........
        ..........
        ..........
        mParent.requestLayout();
        ..........
        ..........
        ..........
    }

主要看兩個,一個是給View設定一個PFLAG_FORCE_LAYOUT標記為,一個就是繼續呼叫ViewParent的requestLayout方法,還是直接看到ViewRootImpl的requestLayout方法

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

可以看到最終也是呼叫了scheduleTraversals()方法通知介面重繪。

invalidate和requestLayout的區別

從最上面的圖中我們可以看到invalidate不會經過measure和layout這兩個過程,我們下面分析下為什麼。
我們看下View的measure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ..........
        ..........
        ..........
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        ..........
        ..........
        ..........
        if (forceLayout || needsLayout) {
        ..........
        ..........
        ..........
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

    }

原始碼裡在requestLayout時對View設定了PFLAG_FORCE_LAYOUT標記位,而invalidate則沒有,所以在呼叫invalidate重繪時forceLayout 為false,也就是最上層的控制元件就不會呼叫onMeasure方法,那麼下面的控制元件也肯定不會進行重新測量。而重新測量的控制元件又會被標記上PFLAG_LAYOUT_REQUIRED,下面看layout方法

public void layout(int l, int t, int r, int b) {
        ..........
        ..........
        ..........
        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);
        }
        ..........
        ..........
        ..........
    }

可以看到如果要呼叫onLayout方法,要麼位置發生了變化,changed為true,要PFLAG_LAYOUT_REQUIRED標記位,所以invalidate的重繪頂層控制元件也不會呼叫onLayout方法,這也就解釋了為什麼最上面的圖裡,invalidate不會經過measure和layout這兩個過程。
在我看來invalidate應該是在控制元件內容或是可見性發生了變化,而其大小位置等不會發生變化時呼叫,所以自然也不需要重新測量佈局,而requestLayout則是大小位置發生了變化則呼叫,使用場景有著區別。