View之invalidate,requestLayout,postInvalidate

目錄介紹

  • 01.invalidate,requestLayout,postInvalidate區別
  • 02.invalidate深刻分析
  • 03.postInvalidate深刻分析
  • 04.requestLayout深刻分析
  • 05.ViewRootImpl做用分析
  • 06.這幾個方法總結

好消息

01.requestLayout、invalidate與postInvalidate做用與區別

  • invalidate() postInvalidate()php

    • 共同點:都是調用onDraw()方法,而後去達到重繪view的目的
    • 區別:invalidate()用於主線程,postInvalidate()用於子線程【經過handler發送消息,在handleMessage中((View) msg.obj).invalidate(),】
  • requestLayout()java

    • 也能夠達到重繪view的目的,可是與前二者不一樣,它會先調用onLayout()從新排版,再調用ondraw()方法。
    • 當view肯定自身已經再也不適合現有的區域時,該view自己調用這個方法要求parent view(父類的視圖)從新調用他的onMeasure、onLayout來從新設置本身位置。特別是當view的layoutparameter發生改變,而且它的值還沒能應用到view上時,這時候適合調用這個方法requestLayout()。 requestLayout調用onMeasure和onLayout,不必定調用onDraw

02.invalidate深刻分析

  • 看看invalidate源碼,以下所示git

    • invalidateCache爲true表示所有重繪。View的invalidate方法最後調用的是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方法github

    • 在ViewGroup的invalidateChild方法中有一個循環,循環裏面會一直調用父佈局的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) {
                //這是一個從當前的佈局View向上不斷遍歷當前佈局View的父佈局,最後遍歷到ViewRootImpl的循環
                do {
                    View view = null;
                    if (parent instanceof View) {
                        view = (View) parent;
                    }
                    
                    //這裏調用的是父佈局的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) {
                    ...
                    //這裏也是一些計算繪製區域的內容
                    return mParent;
                } else {
                    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
                    //這裏也是一些計算繪製區域的內容
                    return mParent;
                }
            }
            return null;
        }
    }
  • View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最後異曲同工,都會調用到ViewRootImpl中的方法面試

    • 能夠看到在ViewRootImpl中最後都會調用scheduleTraversals方法進行繪製。按照對於View的繪製過程的理解,View的繪製流程是從ViewRootImpl的performTraversals方法開始的
    public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
        
        //若是View沒有父佈局,那invalidateInternal方法就會調用這個方法
        @Override
        public void invalidateChild(View child, Rect dirty) {
            invalidateChildInParent(null, dirty);
        }
    
        //ViewGroup的invalidateChild方法最後會調用到這裏
        @Override
        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            //若是dirty爲null就表示要重繪當前ViewRootImpl指示的整個區域
            if (dirty == null) {
                invalidate();
                return null;
            //若是dirty爲empty則表示通過計算須要重繪的區域不須要繪製
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
            return null;
        }   
        
        private void invalidateRectOnScreen(Rect dirty) {
            final Rect localDirty = mDirty;
            ...
            if (!mWillDrawSoon && (intersected || mIsAnimating)) {
                //調用scheduleTraversals方法進行繪製
                scheduleTraversals();
            }
        }
        
        //繪製整個ViewRootImpl區域
        void invalidate() {
            mDirty.set(0, 0, mWidth, mHeight);
            if (!mWillDrawSoon) {
                //調用scheduleTraversals方法進行繪製
                scheduleTraversals();
            }
        }
    }
  • 下面咱們來看看ViewRootImpl中的scheduleTraversals方法segmentfault

    • 看到scheduleTraversals方法中調用了mChoreographer.postCallback方法
    • Choreoprapher類的做用是編排輸入事件、動畫事件和繪製事件的執行,它的postCallback方法的做用就是將要執行的事件放入內部的一個隊列中,最後會執行傳入的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這個類作了什麼?markdown

    • 能夠看到最終調用了performTraversals()方法進行繪製
    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;
            }
        }
    }
  • 大概總結一下數據結構

    • invalidate方法最終調用的是ViewRootImpl中的performTraversals來從新繪製View
    • 在自定義View時,當須要刷新View時,若是是在UI線程中,那就直接調用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方法最後調用了ViewRootImpl的dispatchInvalidateDelayed方法ide

    • ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler發送了一個MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一個內部Handler類
    //發送消息
    //更多內容: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;
            }
        }
    }
  • 大概總結一下

    • postInvalidate方法調用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler發送一個消息,最後調用的仍是View的invalidate方法。
    • 由於ViewRootImpl是在UI線程的,因此postInvalidate方法的做用就是將非UI線程的刷新操做切換到UI線程,以便在UI線程中調用invalidate方法刷新View。因此若是咱們自定義的View自己就是在UI線程,沒有用到多線程的話,直接用invalidate方法來刷新View就能夠了。而咱們平時自定義View基本上都沒有開起其餘線程,因此這就是咱們不多接觸postInvalidate方法的緣由。
    • 一句話總結postInvalidate方法的做用就是:實現了消息機制,可使咱們在非UI線程也能調用刷新View的方法。

04.requestLayout深刻分析

  • 源碼以下所示

    • 在View的requestLayout方法中,首先會設置View的標記位,PFLAG_FORCE_LAYOUT表示當前View要進行從新佈局,PFLAG_INVALIDATED表示要進行從新繪製。
    • requestLayout方法中會一層層向上調用父佈局的requestLayout方法,設置PFLAG_FORCE_LAYOUT標記,最終調用的是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;
        }
    
        //設置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;
        }
    }
  • 而後看看ViewRootImpl中的requestLayout方法

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

    • 因爲requestLayout方法設置了PFLAG_FORCE_LAYOUT標記位,因此measure方法就會調用onMeasure方法對View進行從新測量。在measure方法中最後設置了PFLAG_LAYOUT_REQUIRED標記位,這樣在layout方法中就會執行onLayout方法進行佈局流程。
    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) {
                //調用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;
            }
    
            //設置PFLAG_LAYOUT_REQUIRED標記位,用於layout方法
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    }
  • 再而後看看layout方法

    • 因爲measure方法中設置了PFLAG_LAYOUT_REQUIRED標記位,因此在layout方法中onLayout方法會被調用執行佈局流程。最後清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED標記位。
    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;
    }

05.ViewRootImpl做用分析

  • 連接WindowManager和DecorView的紐帶,另外View的繪製也是經過ViewRootImpl來完成的。

    • 它的主要做用個人總結爲以下:
    • A:連接WindowManager和DecorView的紐帶,更廣一點能夠說是Window和View之間的紐帶。
    • B:完成View的繪製過程,包括measure、layout、draw過程。
    • C:向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件。

06.這幾個方法總結

  • requestLayout方法會標記PFLAG_FORCE_LAYOUT,而後一層層往上調用父佈局的requestLayout方法並標記PFLAG_FORCE_LAYOUT,最後調用ViewRootImpl中的requestLayout方法開始View的三大流程,而後被標記的View就會進行測量、佈局和繪製流程,調用的方法爲onMeasure、onLayout和onDraw。
  • invalidate方法咱們分析過,它的過程和requestLayout方法方法很像,可是invalidate方法沒有標記PFLAG_FORCE_LAYOUT,因此不會執行測量和佈局流程,而只是對須要重繪的View進行重繪,也就是隻會調用onDraw方法,不會調用onMeasure和onLayout方法。

其餘介紹

01.關於博客彙總連接

02.關於個人博客

相關文章
相關標籤/搜索