Android invalidate()源碼分析

Android View源碼中的invalidate()在開發中經常使用,尤其是自定義控件,還有View的動畫基本都是直接調用該方法引起重繪。該方法會使View全部或者部分重繪,具體取決於傳入參數、View透明度、View是否在動畫以及View是否開啓硬件加速繪製等。其主要調用流程如下:

invalidate()分析
簡單總結如下:

1.直接調用invalidate()函數,其內部實際是調用invalidate(true)。這裏的參數代表是否是完全重繪。完全重繪與否其實就是是否使用繪製緩存去繪製的問題。

2.在invalidate(true)函數中:

(1)首先去確認該View是否需要跳過繪製,這裏其實就是一個判斷條件。跳過繪製條件:View不是可見的 && 存在動畫對象 && 父視圖不是ViewGroup或者不是過渡態。只有滿足以上條件纔可以跳過繪製直接返回。

(2)接着要確認在如下條件纔可以重繪:正在動畫或者View大小不是0 || 需要完整繪製繪製並且繪製緩存可用 || 未重繪過 || 透明度和上次比較有了變化。滿足以上條件中的一個就可以執行重繪。

(3)對是否完全重繪設置,即該函數傳入的參數。其實就是設置繪製緩存不可用的標識到mPrivateFlags。在繪製的時候會根據該標識決定是否使用繪製緩存。當然如果是繪製緩存可用的時候需要在繪製的時候先繪製View到Bitmap緩存,如果已經存在該緩存就直接將緩存繪製到canvas。如果是完整重繪就跳過繪製緩存和使用繪製緩存的步驟直接去重繪View到canvas。

(4)接下來就要去ViewParent中去執行。在執行之前分了兩種情況去處理,分別是硬件加速可用和不可用的情況。在硬件加速可用的情況傳入invalidateChild()的dirty參數爲null,代表需要重繪這個View層級。而使用軟件繪製時傳入的是根據View大小構造的dirty矩陣。需要指出這裏的ViewParent一般是值ViewGroup。這裏只分析軟件繪製。

3.在ViewGroup中去執行invalidateChild():

(1)初始化變量:是否在動畫中、View變化矩陣、是否不透明、記錄子View相對於父ViewGroup的相對座標。不透明判斷主要是根據:需要重繪child的不透明度 && 無動畫 && child變化矩陣無變化。記錄座標主要是爲了將帶繪製的dirty矩陣與ViewGroup可顯示矩陣做相交或者相併處理。

(2)處理child變化矩形與ViewGroup變換對象對dirty矩陣的影響。首先對ViewGroup設置的靜態變換對象做處理,獲取設置的變換對象然後獲取其變換矩陣,將child的原矩陣和獲取的矩陣做合併處理,最後將處理之後的矩陣應用到dirty矩陣,同時也將child的變化矩陣應用到dirty矩陣。

(3)do-while循環設置ViewGroup屬性同時處理該child的dirty矩陣與ViewGroup可顯示矩陣的關係:

  • 如果正在動畫設置分ViewParent類型設置動畫標識。

  • 設置ViewGroup的DIRTY標識待重繪。

  • 調用invalidateChildInParent()處理該child的dirty矩陣與ViewGroup可顯示矩陣的關係,同時返回該ViewGroup的ViewParent以便下次循環接着調用。

  • 經過上面步驟的dirty矩陣此時已經與ViewGroup可顯示矩陣做處理了。如果是ViewGroup將其變化的變換矩陣應用與child的dirty矩陣。

4.在以上循環中調用的invalidateChildInParent()由兩種實現。分別是在ViewGroup中實現和ViewRootImpl實現。該循環結束前的最後一次調用一定是ViewRootImpl的實現。

(1)對ViewGroup中的實現做介紹。

  • 首先判斷條件:存在動畫 || 動畫緩存可用。只有該條件滿足纔會執行否則直接返回null。

  • 接着判斷條件:如果是已完成動畫或者是沒有動畫時。首先將dirty位置座標偏移至相對於父View可視區域原點的座標。接着處理如果不需要剪裁child直接將dirty與ViewGroup整體大小求並集。之所以需要ViewGroup整體大小求並集是因爲涉及到子View動畫,ViewGroup需要重繪自身完整區域。如果需要剪裁child則將dirty矩陣與ViewGroup整體大小求交集,如果不存在交集則直接置空dirty矩陣。接着更新location爲該ViewGroup相對於其ViewParent的相對座標。最後返回其ViewParent。

  • 如果是以上判斷的對立面,即正在動畫中:更新location爲該ViewGroup相對於其ViewParent的相對座標。如果需要剪裁child則直接設置dirty爲該ViewGroup整體的大小。如果不需要直接dirty與整體大小求並集。最後返回其ViewParent。

可以看到以上兩點的處理是VIew是否動畫爲主。如果在動畫最後dirty處理完要麼是ViewGroup整體大小要麼比他大。如果不在動畫則有可能小於ViewGroup整體大小。這也是子View動畫會完全影響到其ViewGroup繪製的原因。

(2)對ViewRootImpl的實現介紹。

  • 首先檢測該線程是否是UI線程,如果不是直接拋出異常。

  • 對dirty檢查處理。如果是null,直接設置dirty爲整個大小重繪。如果是空並且不在動畫直接返回null。如果ViewRoot存在偏移mCurScrollY轉化dirty位置到相對於ViewRoot可視區域的座標。

  • 將dirty添加到待重繪區域。獲取上次重繪的矩陣localDirty與dirty求並集。處理窗口縮放然後將處理後的大小與localDirty求交集,如果沒有交集置空localDirty。

  • 重繪任務。滿足如下條件:performTraversal()未執行 && localDirty與窗口存在交集或者正在動畫。調用任務scheduleTraversals();最後返回null。

所以最後一次do循環一定是調用ViewRootImpl中的該方法實現,最後執行繪製任務的。

至此,invalidate()源碼介紹完畢。

發佈了40 篇原創文章 · 獲贊 30 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章