requestLayout方法和invalidate 方法比較

requestLayout方法和invalidate方法

自定義View的時候難免會和這2個方法打交道,在之前的文章中我們已經介紹了invalidate方法,今天我們來對比下requestLayout方法和invalidate方法有什麼不同之處

等不及的先給個結論:

requestLayout方法會導致View的onMeasure、onLayout、onDraw方法被調用;invalidate方法則只會導致View的onDraw方法被調用

requestLayout方法源碼分析
//View.class
@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;
}

//設置PFLAG_FORCE_LAYOUT標記位,這樣就會導致重新測量和佈局
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
//設置PFLAG_INVALIDATED就會進行重新繪製
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
    //不斷調用上層View的requestLayout方法
    mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
    mAttachInfo.mViewRequestingLayout = null;
}

}
在View的requestLayout方法中,首先會設置View的標記位,PFLAG_FORCE_LAYOUT表示當前View要進行重新佈局,PFLAG_INVALIDATED表示要進行重新繪製。

requestLayout方法中會一層層向上調用父佈局的requestLayout方法,設置PFLAG_FORCE_LAYOUT標記,最終調用的是ViewRootImpl中的requestLayout方法。

//ViewRootImpl.class
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
可以看到ViewRootImpl中的requestLayout方法中會調用scheduleTraversals方法,scheduleTraversals方法最後會調用performTraversals方法開始執行View的三大流程,會分別調用View的measure、layout、draw方法。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

...
if (forceLayout || needsLayout) {
    // first clears the measured dimension flag
    mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

    resolveRtlPropertiesIfNeeded();

    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        // measure ourselves, this should set the measured dimension flag back
        //調用onMeasure方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } else {
        long value = mMeasureCache.valueAt(cacheIndex);
        // Casting a long to int drops the high 32 bits, no mask needed
        setMeasuredDimensionRaw((int) (value >> 32), (int) value);
        mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    
    //設置PFLAG_LAYOUT_REQUIRED標記位,用於layout方法
    mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

...

}
由於requestLayout方法設置了PFLAG_FORCE_LAYOUT標記位,所以measure方法就會調用onMeasure方法對View進行重新測量。在measure方法中最後設置了PFLAG_LAYOUT_REQUIRED標記位,這樣在layout方法中就會執行onLayout方法進行佈局流程。

public void layout(int l, int t, int r, int b) {

//由於measure方法中設置了PFLAG_LAYOUT_REQUIRED標記位,所以會進入調用onLayout方法進行佈局流程
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    onLayout(changed, l, t, r, b);

    if (shouldDrawRoundScrollbar()) {
        if(mRoundScrollbarRenderer == null) {
            mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
        }
    } else {
        mRoundScrollbarRenderer = null;
    }

    //取消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;

}
由於measure方法中設置了PFLAG_LAYOUT_REQUIRED標記位,所以在layout方法中onLayout方法會被調用執行佈局流程。最後清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED標記位。

總結
(1)requestLayout方法會標記PFLAG_FORCE_LAYOUT,然後一層層往上調用父佈局的requestLayout方法並標記PFLAG_FORCE_LAYOUT,最後調用ViewRootImpl中的requestLayout方法開始View的三大流程,然後被標記的View就會進行測量、佈局和繪製流程,調用的方法爲onMeasure、onLayout和onDraw。

(2)invalidate方法我們分析過,它的過程和requestLayout方法方法很像,但是invalidate方法沒有標記PFLAG_FORCE_LAYOUT,所以不會執行測量和佈局流程,而只是對需要重繪的View進行重繪,也就是隻會調用onDraw方法,不會調用onMeasure和onLayout方法。

測試
簡單自定義一個View,然後設計2個按鈕,點擊後分別調用View的requestLayout方法和invalidate方法,查看日誌。

測試界面
點擊invalidate按鈕後調用View的invalidate方法

可以看見只有onDraw方法被調用
點擊layout按鈕調用View的requestLayout方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章