摘要:因為是在線程的,所以方法的作用就是將非線程的刷新操作切換到線程,以便在線程中調用方法刷新。一句話總結方法的作用就是實現了消息機制,可以使我們在非線程也能調用刷新的方法。
目錄介紹
01.invalidate,requestLayout,postInvalidate區別
02.invalidate深入分析
03.postInvalidate深入分析
04.requestLayout深入分析
05.ViewRootImpl作用分析
06.這幾個方法總結
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
01.Java基礎問題(19個)
02.Java面向對象問題(10個)
03.Java數據結構問題(20個)
04.JavaIO流問題(6個)
05.java多線程問題(19)
06.Java虛擬機問題(10個)
07.Java類加載問題(8個)
08.Java反射問題(6個)
10.Java異常問題(9個)
01.requestLayout、invalidate與postInvalidate作用與區別
invalidate() postInvalidate()
共同點:都是調用onDraw()方法,然后去達到重繪view的目的
區別:invalidate()用于主線程,postInvalidate()用于子線程【通過handler發送消息,在handleMessage中((View) msg.obj).invalidate(),】
requestLayout()
也可以達到重繪view的目的,但是與前兩者不同,它會先調用onLayout()重新排版,再調用ondraw()方法。
當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view(父類的視圖)重新調用他的onMeasure、onLayout來重新設置自己位置。特別是當view的layoutparameter發生改變,并且它的值還沒能應用到view上時,這時候適合調用這個方法requestLayout()。 requestLayout調用onMeasure和onLayout,不一定調用onDraw
02.invalidate深入分析
看看invalidate源碼,如下所示
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方法
在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方法
看到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這個類做了什么?
可以看到最終調用了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方法
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) { ArrayList05.ViewRootImpl作用分析listenersCopy = (ArrayList )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; }
鏈接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.關于博客匯總鏈接1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
掘金:https://juejin.im/user/593943...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73845.html
摘要:這種自定義控件在原生控件提供的方法外,可以自己添加一些方法。從頂層父到子遞歸調用方法,方法又回調。 目錄介紹 3.0.0.1 View的繪制需要經過哪些過程?有哪些常用回調方法?View的繪制流程的詳細流程是怎樣的? 3.0.0.2 View繪制流程,當一個TextView的實例調用setText()方法后執行了什么?請說一下原理…… 3.0.0.3 requestLayout()、...
閱讀 2204·2021-09-02 15:11
閱讀 1513·2019-08-30 15:43
閱讀 2081·2019-08-29 13:48
閱讀 2798·2019-08-26 13:55
閱讀 2107·2019-08-23 15:09
閱讀 2905·2019-08-23 14:40
閱讀 3433·2019-08-23 14:23
閱讀 2642·2019-08-23 14:20