View之invalidate,requestLayout,postInvalidate

目錄介紹

  • 01.invalidate,requestLayout,postInvalidate區(qū)別
  • 02.invalidate深入分析
  • 03.postInvalidate深入分析
  • 04.requestLayout深入分析
  • 05.ViewRootImpl作用分析
  • 06.這幾個方法總結(jié)

好消息

01.requestLayout、invalidate與postInvalidate作用與區(qū)別

  • invalidate() postInvalidate()
    • 共同點:都是調(diào)用onDraw()方法,然后去達(dá)到重繪view的目的
    • 區(qū)別:invalidate()用于主線程,postInvalidate()用于子線程【通過handler發(fā)送消息,在handleMessage中((View) msg.obj).invalidate(),】
  • requestLayout()
    • 也可以達(dá)到重繪view的目的,但是與前兩者不同,它會先調(diào)用onLayout()重新排版,再調(diào)用ondraw()方法。
    • 當(dāng)view確定自身已經(jīng)不再適合現(xiàn)有的區(qū)域時,該view本身調(diào)用這個方法要求parent view(父類的視圖)重新調(diào)用他的onMeasure、onLayout來重新設(shè)置自己位置。特別是當(dāng)view的layoutparameter發(fā)生改變,并且它的值還沒能應(yīng)用到view上時,這時候適合調(diào)用這個方法requestLayout()。 requestLayout調(diào)用onMeasure和onLayout,不一定調(diào)用onDraw

02.invalidate深入分析

  • 看看invalidate源碼,如下所示
    • invalidateCache為true表示全部重繪。View的invalidate方法最后調(diào)用的是invalidateInternal方法,invalidateInternal方法中的重點邏輯在源碼上添加注釋呢。
    public void invalidate() {
        invalidate(true);
    }
    
    
    public 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;
        }
    
        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();
                }
            }
        }
    }
    
  • 看看ViewGroup中的invalidateChild方法
    • 在ViewGroup的invalidateChild方法中有一個循環(huán),循環(huán)里面會一直調(diào)用父布局的invalidateChildInParent方法,而View和ViewGroup的最終父布局都是ViewRootImpl。
    @UiThread
    public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
        @Override
        public final void invalidateChild(View child, final Rect dirty) {
            ViewParent parent = this;
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                //這是一個從當(dāng)前的布局View向上不斷遍歷當(dāng)前布局View的父布局,最后遍歷到ViewRootImpl的循環(huán)
                do {
                    View view = null;
                    if (parent instanceof View) {
                        view = (View) parent;
                    }
                    
                    //這里調(diào)用的是父布局的invalidateChildInParent方法
                    parent = parent.invalidateChildInParent(location, dirty);
                } while (parent != null);
            }
        }
        
        @Override
        public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
            if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                    (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
                if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                            FLAG_OPTIMIZE_INVALIDATE) {
                    ...
                    //這里也是一些計算繪制區(qū)域的內(nèi)容
                    return mParent;
                } else {
                    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
                    //這里也是一些計算繪制區(qū)域的內(nèi)容
                    return mParent;
                }
            }
            return null;
        }
    }
    
  • View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最后殊途同歸,都會調(diào)用到ViewRootImpl中的方法
    • 可以看到在ViewRootImpl中最后都會調(diào)用scheduleTraversals方法進(jìn)行繪制。按照對于View的繪制過程的理解,View的繪制流程是從ViewRootImpl的performTraversals方法開始的
    public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
        
        //如果View沒有父布局,那invalidateInternal方法就會調(diào)用這個方法
        @Override
        public void invalidateChild(View child, Rect dirty) {
            invalidateChildInParent(null, dirty);
        }
    
        //ViewGroup的invalidateChild方法最后會調(diào)用到這里
        @Override
        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            //如果dirty為null就表示要重繪當(dāng)前ViewRootImpl指示的整個區(qū)域
            if (dirty == null) {
                invalidate();
                return null;
            //如果dirty為empty則表示經(jīng)過計算需要重繪的區(qū)域不需要繪制
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
            return null;
        }   
        
        private void invalidateRectOnScreen(Rect dirty) {
            final Rect localDirty = mDirty;
            ...
            if (!mWillDrawSoon && (intersected || mIsAnimating)) {
                //調(diào)用scheduleTraversals方法進(jìn)行繪制
                scheduleTraversals();
            }
        }
        
        //繪制整個ViewRootImpl區(qū)域
        void invalidate() {
            mDirty.set(0, 0, mWidth, mHeight);
            if (!mWillDrawSoon) {
                //調(diào)用scheduleTraversals方法進(jìn)行繪制
                scheduleTraversals();
            }
        }
    }
    
  • 下面我們來看看ViewRootImpl中的scheduleTraversals方法
    • 看到scheduleTraversals方法中調(diào)用了mChoreographer.postCallback方法
    • Choreoprapher類的作用是編排輸入事件、動畫事件和繪制事件的執(zhí)行,它的postCallback方法的作用就是將要執(zhí)行的事件放入內(nèi)部的一個隊列中,最后會執(zhí)行傳入的Runnable,這里也就是mTraversalRunnable。
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
  • 來看看TraversalRunnable這個類做了什么?
    • 可以看到最終調(diào)用了performTraversals()方法進(jìn)行繪制
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    
  • 大概總結(jié)一下
    • invalidate方法最終調(diào)用的是ViewRootImpl中的performTraversals來重新繪制View
    • 在自定義View時,當(dāng)需要刷新View時,如果是在UI線程中,那就直接調(diào)用invalidate方法,如果是在非UI線程中,那就通過postInvalidate方法來刷新View

03.postInvalidate深入分析

  • 先來看看View中的postInvalidate方法
    @UiThread
    public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
        
        ...
        
        public void postInvalidate() {
            postInvalidateDelayed(0);
        }
        
        public void postInvalidate(int left, int top, int right, int bottom) {
            postInvalidateDelayed(0, left, top, right, bottom);
        }
        
        public void postInvalidateDelayed(long delayMilliseconds) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
            }
        }
        ...      
    }
    
  • 可以看到,postInvalidate方法最后調(diào)用了ViewRootImpl的dispatchInvalidateDelayed方法
    • ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler發(fā)送了一個MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一個內(nèi)部Handler類
    //發(fā)送消息
    //更多內(nèi)容:https://github.com/yangchong211
    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }
    
    //接收消息
    final class ViewRootHandler extends Handler {
        @Override
        public String getMessageName(Message message) {
            switch (message.what) {
                case MSG_INVALIDATE:
                    return "MSG_INVALIDATE";
            }
            return super.getMessageName(message);
        }
    
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            }
        }
    }
    
  • 大概總結(jié)一下
    • postInvalidate方法調(diào)用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler發(fā)送一個消息,最后調(diào)用的還是View的invalidate方法。
    • 因為ViewRootImpl是在UI線程的,所以postInvalidate方法的作用就是將非UI線程的刷新操作切換到UI線程,以便在UI線程中調(diào)用invalidate方法刷新View。所以如果我們自定義的View本身就是在UI線程,沒有用到多線程的話,直接用invalidate方法來刷新View就可以了。而我們平時自定義View基本上都沒有開起其他線程,所以這就是我們很少接觸postInvalidate方法的原因。
    • 一句話總結(jié)postInvalidate方法的作用就是:實現(xiàn)了消息機制,可以使我們在非UI線程也能調(diào)用刷新View的方法。

04.requestLayout深入分析

  • 源碼如下所示
    • 在View的requestLayout方法中,首先會設(shè)置View的標(biāo)記位,PFLAG_FORCE_LAYOUT表示當(dāng)前View要進(jìn)行重新布局,PFLAG_INVALIDATED表示要進(jìn)行重新繪制。
    • requestLayout方法中會一層層向上調(diào)用父布局的requestLayout方法,設(shè)置PFLAG_FORCE_LAYOUT標(biāo)記,最終調(diào)用的是ViewRootImpl中的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)記位,這樣就會導(dǎo)致重新測量和布局
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        //設(shè)置PFLAG_INVALIDATED就會進(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;
        }
    }
    
  • 然后看看ViewRootImpl中的requestLayout方法
    • 可以看到ViewRootImpl中的requestLayout方法中會調(diào)用scheduleTraversals方法,scheduleTraversals方法最后會調(diào)用performTraversals方法開始執(zhí)行View的三大流程,會分別調(diào)用View的measure、layout、draw方法。
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
  • 然后再看看measure測量方法
    • 由于requestLayout方法設(shè)置了PFLAG_FORCE_LAYOUT標(biāo)記位,所以measure方法就會調(diào)用onMeasure方法對View進(jìn)行重新測量。在measure方法中最后設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,這樣在layout方法中就會執(zhí)行onLayout方法進(jìn)行布局流程。
    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;
    
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //調(diào)用onMeasure方法
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            //設(shè)置PFLAG_LAYOUT_REQUIRED標(biāo)記位,用于layout方法
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    }
    
  • 再然后看看layout方法
    • 由于measure方法中設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,所以在layout方法中onLayout方法會被調(diào)用執(zhí)行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED標(biāo)記位。
    public void layout(int l, int t, int r, int b) {
        //由于measure方法中設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記位,所以會進(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;
    }
    

05.ViewRootImpl作用分析

  • 鏈接WindowManager和DecorView的紐帶,另外View的繪制也是通過ViewRootImpl來完成的。
    • 它的主要作用我的總結(jié)為如下:
    • A:鏈接WindowManager和DecorView的紐帶,更廣一點可以說是Window和View之間的紐帶。
    • B:完成View的繪制過程,包括measure、layout、draw過程。
    • C:向DecorView分發(fā)收到的用戶發(fā)起的event事件,如按鍵,觸屏等事件。

06.這幾個方法總結(jié)

  • requestLayout方法會標(biāo)記PFLAG_FORCE_LAYOUT,然后一層層往上調(diào)用父布局的requestLayout方法并標(biāo)記PFLAG_FORCE_LAYOUT,最后調(diào)用ViewRootImpl中的requestLayout方法開始View的三大流程,然后被標(biāo)記的View就會進(jìn)行測量、布局和繪制流程,調(diào)用的方法為onMeasure、onLayout和onDraw。
  • invalidate方法我們分析過,它的過程和requestLayout方法方法很像,但是invalidate方法沒有標(biāo)記PFLAG_FORCE_LAYOUT,所以不會執(zhí)行測量和布局流程,而只是對需要重繪的View進(jìn)行重繪,也就是只會調(diào)用onDraw方法,不會調(diào)用onMeasure和onLayout方法。

其他介紹

01.關(guān)于博客匯總鏈接

02.關(guān)于我的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,914評論 2 372

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