View之invalidate,requestLayout,postInvalidate

目錄介紹

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

好消息

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

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

02.invalidate深入分析

  • 看看invalidate源碼,如下所示
    • invalidateCache為true表示全部重繪。View的invalidate方法最后調(diào)用的是invalidateInternal方法,invalidateInternal方法中的重點(diǎn)邏輯在源碼上添加注釋呢。
    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;
            }
    
            //這個(gè)地方是重點(diǎn)邏輯,主要分析這個(gè)
            // 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方法中有一個(gè)循環(huán),循環(huán)里面會(huì)一直調(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) {
                //這是一個(gè)從當(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) {
                    ...
                    //這里也是一些計(jì)算繪制區(qū)域的內(nèi)容
                    return mParent;
                } else {
                    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
                    //這里也是一些計(jì)算繪制區(qū)域的內(nèi)容
                    return mParent;
                }
            }
            return null;
        }
    }
    
  • View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最后殊途同歸,都會(huì)調(diào)用到ViewRootImpl中的方法
    • 可以看到在ViewRootImpl中最后都會(huì)調(diào)用scheduleTraversals方法進(jìn)行繪制。按照對(duì)于View的繪制過程的理解,View的繪制流程是從ViewRootImpl的performTraversals方法開始的
    public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
        
        //如果View沒有父布局,那invalidateInternal方法就會(huì)調(diào)用這個(gè)方法
        @Override
        public void invalidateChild(View child, Rect dirty) {
            invalidateChildInParent(null, dirty);
        }
    
        //ViewGroup的invalidateChild方法最后會(huì)調(diào)用到這里
        @Override
        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            //如果dirty為null就表示要重繪當(dāng)前ViewRootImpl指示的整個(gè)區(qū)域
            if (dirty == null) {
                invalidate();
                return null;
            //如果dirty為empty則表示經(jīng)過計(jì)算需要重繪的區(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();
            }
        }
        
        //繪制整個(gè)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類的作用是編排輸入事件、動(dòng)畫事件和繪制事件的執(zhí)行,它的postCallback方法的作用就是將要執(zhí)行的事件放入內(nèi)部的一個(gè)隊(duì)列中,最后會(huì)執(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這個(gè)類做了什么?
    • 可以看到最終調(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時(shí),當(dāng)需要刷新View時(shí),如果是在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ā)送了一個(gè)MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一個(gè)內(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ā)送一個(gè)消息,最后調(diào)用的還是View的invalidate方法。
    • 因?yàn)閂iewRootImpl是在UI線程的,所以postInvalidate方法的作用就是將非UI線程的刷新操作切換到UI線程,以便在UI線程中調(diào)用invalidate方法刷新View。所以如果我們自定義的View本身就是在UI線程,沒有用到多線程的話,直接用invalidate方法來刷新View就可以了。而我們平時(shí)自定義View基本上都沒有開起其他線程,所以這就是我們很少接觸postInvalidate方法的原因。
    • 一句話總結(jié)postInvalidate方法的作用就是:實(shí)現(xiàn)了消息機(jī)制,可以使我們?cè)诜荱I線程也能調(diào)用刷新View的方法。

04.requestLayout深入分析

  • 源碼如下所示
    • 在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方法。
    //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;
        }
    }
    
  • 然后看看ViewRootImpl中的requestLayout方法
    • 可以看到ViewRootImpl中的requestLayout方法中會(huì)調(diào)用scheduleTraversals方法,scheduleTraversals方法最后會(huì)調(diào)用performTraversals方法開始執(zhí)行View的三大流程,會(huì)分別調(diào)用View的measure、layout、draw方法。
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
  • 然后再看看measure測(cè)量方法
    • 由于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 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方法會(huì)被調(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)記位,所以會(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;
    }
    

05.ViewRootImpl作用分析

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

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

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

其他介紹

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

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

?著作權(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)容