View 繪制體系知識梳理(6) - 繪制過程之 requestLayout 和 invalidate 詳解

一刽脖、概述

經(jīng)過前面三篇文章的分析:

對于繪制的整個分發(fā)過程已經(jīng)有了一個大致的了解畔塔,我們可以發(fā)現(xiàn)一個規(guī)律恼琼,無論是測量掩浙、布局還是繪制,對于任何一個View/Group來說萄喳,它都是一個至上而下的遞歸事件調(diào)用芬骄,直到到達整個View樹的葉節(jié)點為止猾愿。
下面,我們來分析幾個平時常用的方法:

  • requestLayout
  • invalidate
  • postInvalidate

二德玫、requestLayout

requestLayout是在View中定義的匪蟀,并且在ViewGroup中沒有重寫該方法,它的注釋是這樣解釋的:在需要刷新View的布局時調(diào)用這個函數(shù)宰僧,它會安排一個布局的傳遞材彪。我們不應(yīng)該在布局的過程中(isInLayout())調(diào)用這個函數(shù)观挎,如果當前正在布局,那么這一請求有可能在以下時刻被執(zhí)行:當前布局結(jié)束段化、當前幀被繪制完或者下次布局發(fā)生時嘁捷。

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * <p>Subclasses which override this method should call the superclass method to
     * handle possible request-during-layout errors correctly.</p>
     */
    @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;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

在上面的代碼當中,設(shè)置了兩個標志位:PFLAG_FORCE_LAYOUT/PFLAG_INVALIDATED显熏,除此之外最關(guān)鍵的一句話是:

protected ViewParent mParent;
//....
mParent.requestLayout();

這個mParent存儲的時候該View所對應(yīng)的父節(jié)點雄嚣,而當調(diào)用父節(jié)點的requestLayout()時,它又會調(diào)用它的父節(jié)點的requestLayout喘蟆,就這樣缓升,以調(diào)用requestLayoutView為起始節(jié)點,一步步沿著View樹傳遞上去蕴轨,那么這個過程什么時候會終止呢港谊?
根據(jù)前面的分析,我們知道整個View樹的根節(jié)點是DecorView橙弱,那么我們需要看一下DecorViewmParent變量是什么歧寺,回到ViewRootImplsetView方法當中,有這么一句:

view.assignParent(this);

因此棘脐,DecorView中的mParent就是ViewRootImpl斜筐,而ViewRootImpl中的mView就是DecorView,所以蛀缝,這一傳遞過程的終點就是ViewRootImplrequestLayout方法:

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

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //該Runnable進行操作doTraversal.
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //這里最終會進行布局.
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

其中scheduleTraversals()中會執(zhí)行一個mTraversalRunnable顷链,該Runnable中最終會調(diào)用doTraversal,而doTraversal中執(zhí)行的就是我們前面一直在談到的performTraversals屈梁。
那么蕴潦,前面我們分析過,performTraversalsmeasure方法會從根節(jié)點調(diào)用子節(jié)點的測量操作俘闯,并依次傳遞下去,那么是否所有的子View都有必要重新測量呢忽冻,這就需要我們在調(diào)用ViewrequestLayout是設(shè)置的標志位PFLAG_FORCE_LAYOUT來判斷真朗,在measure當中,調(diào)用onMeasure之前僧诚,會有這么一個判斷條件:

if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
    onMeasure(widthMeasureSpec, heightMeasureSpec);
}

這個標志位會在layout完成之后被恢復(fù):

    public void layout(int l, int t, int r, int b) {
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
        }
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

在進行完layout之后遮婶,requestLayout()所引發(fā)的過程就此終止了,它不會調(diào)用draw湖笨,不會重新繪制任何視圖包括該調(diào)用者本身旗扑。

三、invalidate

invalidate最終會調(diào)用到下面這個方法:

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }

        if (skipInvalidate()) {
            return;
        }

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

            mPrivateFlags |= PFLAG_DIRTY;

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

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

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

            // Damage the entire IsolatedZVolume receiving this view's shadow.
            if (isHardwareAccelerated() && getZ() != 0) {
                damageShadowReceiver();
            }
        }
    }

其中慈省,關(guān)鍵的一句是:

p.invalidateChild(this, damage);

在這里臀防,p一定不為空并且它一定是一個ViewGroup,那么我們來看一下ViewGroup的這個方法:

public final void invalidateChild(View child, final Rect dirty) {
    do {
        parent = parent.invalidateChildInParent(location, dirty);
    } while (parent != null);
}

ViewGroup當中的invalidateChildInParent會根據(jù)傳入的區(qū)域來決定自己的繪制區(qū)域,和requestLayout類似袱衷,最終會調(diào)用ViewRootImpl的該方法:

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(TAG, "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);
            }
        }
        invalidateRectOnScreen(dirty);
        return null;
    }

這其中又會調(diào)用invalidate

    void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

這里捎废,最終又會走到前面說的performTraversals()方法,請求重繪View樹致燥,即draw()過程登疗,假如視圖發(fā)生大小沒有變化就不會調(diào)用layout()過程,并且只繪制那些需要重繪的視圖嫌蚤。

三辐益、其它知識點

  • invalidate,請求重新draw脱吱,只會繪制調(diào)用者本身智政。
  • setSelection,同上急凰。
  • setVisibility:當ViewINVISIBLE變?yōu)?code>VISIBILE女仰,會間接調(diào)用invalidate方法,繼而繪制該View抡锈,而從INVISIBLE/VISIBLE變?yōu)?code>GONE之后疾忍,由于View樹的大小發(fā)生了變化,會進行measure/layout/draw床三,同樣一罩,他只會繪制需要重繪的視圖。
  • setEnable:請求重新draw撇簿,只會繪制調(diào)用者本身聂渊。
  • requestFocus:請求重新draw,只會繪制需要重繪的視圖四瘫。

四汉嗽、參考文獻

1.http://blog.csdn.net/yanbober/article/details/46128379/
2.http://blog.csdn.net/a553181867/article/details/51583060

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市找蜜,隨后出現(xiàn)的幾起案子饼暑,更是在濱河造成了極大的恐慌,老刑警劉巖洗做,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弓叛,死亡現(xiàn)場離奇詭異,居然都是意外死亡诚纸,警方通過查閱死者的電腦和手機撰筷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畦徘,“玉大人毕籽,你說我怎么就攤上這事抬闯。” “怎么了影钉?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵画髓,是天一觀的道長。 經(jīng)常有香客問我平委,道長奈虾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任廉赔,我火速辦了婚禮肉微,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜡塌。我一直安慰自己碉纳,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布馏艾。 她就那樣靜靜地躺著劳曹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琅摩。 梳的紋絲不亂的頭發(fā)上铁孵,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音房资,去河邊找鬼蜕劝。 笑死,一個胖子當著我的面吹牛轰异,可吹牛的內(nèi)容都是我干的岖沛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搭独,長吁一口氣:“原來是場噩夢啊……” “哼婴削!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牙肝,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馆蠕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惊奇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡播赁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年颂郎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片容为。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡乓序,死狀恐怖寺酪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情替劈,我是刑警寧澤寄雀,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站陨献,受9級特大地震影響盒犹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眨业,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一急膀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧龄捡,春花似錦卓嫂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奸腺,卻和暖如春餐禁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洋机。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工坠宴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绷旗。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓喜鼓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衔肢。 傳聞我的和親對象是個殘疾皇子庄岖,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容