Android自定義View之requestLayout方法和invalidate方法

Android自定義View系列

自定義View的時(shí)候難免會(huì)和這2個(gè)方法打交道,在之前的文章中我們已經(jīng)介紹了invalidate方法,今天我們來對(duì)比下requestLayout方法和invalidate方法有什么不同之處

等不及的先給個(gè)結(jié)論:

requestLayout方法會(huì)導(dǎo)致View的onMeasure、onLayout、onDraw方法被調(diào)用;invalidate方法則只會(huì)導(dǎo)致View的onDraw方法被調(diào)用

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

    //設(shè)置PFLAG_FORCE_LAYOUT標(biāo)記位,這樣就會(huì)導(dǎo)致重新測(cè)量和布局
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    //設(shè)置PFLAG_INVALIDATED就會(huì)進(jìn)行重新繪制
    mPrivateFlags |= PFLAG_INVALIDATED;

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

在View的requestLayout方法中,首先會(huì)設(shè)置View的標(biāo)記位,PFLAG_FORCE_LAYOUT表示當(dāng)前View要進(jìn)行重新布局,PFLAG_INVALIDATED表示要進(jìn)行重新繪制。

requestLayout方法中會(huì)一層層向上調(diào)用父布局的requestLayout方法,設(shè)置PFLAG_FORCE_LAYOUT標(biāo)記,最終調(diào)用的是ViewRootImpl中的requestLayout方法。

//ViewRootImpl.class
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

可以看到ViewRootImpl中的requestLayout方法中會(huì)調(diào)用scheduleTraversals方法,scheduleTraversals方法最后會(huì)調(diào)用performTraversals方法開始執(zhí)行View的三大流程,會(huì)分別調(diào)用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
            //調(diào)用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;
        }

        
        //設(shè)置PFLAG_LAYOUT_REQUIRED標(biāo)記位,用于layout方法
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    ...
}

由于requestLayout方法設(shè)置了PFLAG_FORCE_LAYOUT標(biāo)記位,所以measure方法就會(huì)調(diào)用onMeasure方法對(duì)View進(jìn)行重新測(cè)量。在measure方法中最后設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,這樣在layout方法中就會(huì)執(zhí)行onLayout方法進(jìn)行布局流程。

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

    //由于measure方法中設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,所以會(huì)進(jìn)入調(diào)用onLayout方法進(jìn)行布局流程
    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標(biāo)記位
        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標(biāo)記位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

由于measure方法中設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,所以在layout方法中onLayout方法會(huì)被調(diào)用執(zhí)行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED標(biāo)記位。

總結(jié)

(1)requestLayout方法會(huì)標(biāo)記PFLAG_FORCE_LAYOUT,然后一層層往上調(diào)用父布局的requestLayout方法并標(biāo)記PFLAG_FORCE_LAYOUT,最后調(diào)用ViewRootImpl中的requestLayout方法開始View的三大流程,然后被標(biāo)記的View就會(huì)進(jìn)行測(cè)量、布局和繪制流程,調(diào)用的方法為onMeasure、onLayout和onDraw。

(2)invalidate方法我們分析過,它的過程和requestLayout方法方法很像,但是invalidate方法沒有標(biāo)記PFLAG_FORCE_LAYOUT,所以不會(huì)執(zhí)行測(cè)量和布局流程,而只是對(duì)需要重繪的View進(jìn)行重繪,也就是只會(huì)調(diào)用onDraw方法,不會(huì)調(diào)用onMeasure和onLayout方法。

測(cè)試

簡(jiǎn)單自定義一個(gè)View,然后設(shè)計(jì)2個(gè)按鈕,點(diǎn)擊后分別調(diào)用View的requestLayout方法和invalidate方法,查看日志。

測(cè)試界面

點(diǎn)擊invalidate按鈕后調(diào)用View的invalidate方法


可以看見只有onDraw方法被調(diào)用

點(diǎn)擊layout按鈕調(diào)用View的requestLayout方法


可以看見onMeasure、onLayout、onDraw方法都被調(diào)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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