遍歷View樹意味著整個View需要重新對其包含的子視圖分配大小并重繪,導致重新遍歷的原因主要有三個
1.視圖本身內部狀況變化引起重繪。
2.第二個是View樹內部添加或者刪除了View。
3.View本身的大小及可見性發生變化。
能引起View樹重新遍歷的操作,總的來講可以分為三類。一類是導致視圖大小發生變化;第二類是導致ViewGroup重新為子視圖分配位置;第三類是視圖顯示情況發生變化需要重繪。這三類情況最后都直接或者間接調用到三個函數,分別為invalidate()、requesetLayout()及requestFocus(),而這三個函數最終都會調用到ViewRoot中的schuedeuleTravesals()函數,該函數然后發起一個異步消息,消息處理中調用performTraversals()開始對整個View進行重新遍歷。
能導致調用invalidate()函數的包含三種情況:
1、當應用程序改變視圖顯示屬性時,調用setVisibility()。
2、當改變視圖Selected狀態時,調用setSelected()。
3、當改變視圖Enable狀態時,調用setEnable()函數。
能導致調用requestLayout()函數的情況包含兩種:
1、當應用程序改變視圖顯示屬性時,調用setVisibility(),由于顯示或者不顯示將影響其他兄弟視圖的位置,因 此會調用到requestLayout()。
2、應用程序直接或間接調用該函數,間接調用是指應用程序調用了View類的其他函數,從而間接調用到requestLayout()。
requestFocus()一般由程序直接調用。
refreshDrawableList()
該函數的作用是根據狀態標識,為視圖賦予不同的Drawable對象。
1、給mPrivateFlags添加DRAWABLE_STATE_DIRTY標識,該標識僅在后面調用getDrawableState()函數中用于判斷是否發生狀態變化。
2、調用drawableSateChanged()。該函數是一個protected類型,只有Framework中的View子類可以重載該函數,一般來講,就是ViewGroup重載了該函數。ViewGroup中重載該函數的作用僅僅是為了配合FLAG_ADD_STATES_FROM_CHILDRN標識,后面將會講到該標識的作用。View類內部,該函數的默認實現包括以下幾項。
(1)調用getDrawableState()獲得視圖的當前狀態,然后再調用onCreateDrawableState()將這些狀態轉換為一個int[]型數組,這個數組的內部格式是預先定好的,DrawableStateList類可以識別該int[]數組。最后再將第一步設置的標識進行清除。
(2)mBGDrawable變量是該視圖的背景圖,它包含一個setState()函數,函數的參數正是上一步獲得的int[]型數組,該函數內部會根據該int[]型數組為mBGDrawable找到真正的Drawable對象。
3、如果該視圖有父視圖,則調用父視圖的childDrawableStateChanged()。父視圖要么是ViewGroup類,要么是一個ViewRoot類。
ensureTouchMode()
這個函數的命名不夠準確,從該函數內部分析來看,其作用是在Touch和非Touch直接切換時對視圖的焦點狀態進行處理。
setVisibility()
該函數用于改變視圖的可視狀態,可視狀態包括GONE、VISIBLE、INVSIBLE三種。該函數內部很簡單,首先調用setFlags(),然后調用mBGDrawable.setVisible()函數改變視圖背景圖的顯示狀態。
setEnable()
Enable狀態僅僅是內部的一個邏輯,不會引起重新布局,僅僅是引起視圖的重繪。
1、給mPrivateFlags變量添加ENABLE或者DISABLE標識,這由setEnalbe()的參數決定。
2、調用refreshDrawableState()重新獲取背景圖。
3、調用invalidate()請求View樹重繪。
invalidate()
該函數的作用是請求View樹進行重繪,當應用程序需要重繪某個視圖時,可以調用該函數。大致做了兩件事情。
1、給所有需要重繪的視圖添加了一個DIRTY或者DIRTY_OPAQUE標記。
2、通過矩形運算,找到真正需要重繪的矩形區,并將其保存在了ViewRoot類中的mDirty變量中。
requestFocus()
要想讓某個視圖獲得焦點
1、用戶使用方向鍵將焦點移動到該視圖(其實也是調用requestFocus()函數完成)。
2、直接調用視圖的requestFocus()函數。
下面分析requestFocus(direction, preFocusRect)的執行過程:
1、判斷該視圖是不是FOCUSABLE的,如果不是,則直接返回false。
2、如果當前是Touch模式,但是視圖的FOCUSABLE_IN_TOUCH_MODE卻為false,即該視圖不能在Touch模式下獲得焦點,則直接返回false。
3、調用hasAncestorThatBlockDescendantFocus()判斷是否父視圖阻止該子視圖獲得焦點,如果阻止,則直接返回false。應用程序可以調用ViewGoup的setDescendantFocusability(int focusability)方法設置該ViewGroup是否阻止其子視圖獲得焦點,默認情況下都不阻止。
4、以上三步實際上執行的都是前期檢查,調用handleFocusGainInternal(dir, rect)進行具體的焦點獲取操作,執行完該函數后,則該視圖肯定獲取焦點,所以返回true。
requestLayout()
該函數的執行過程比較簡單,因為當View樹進行重新布局時,總是重新給所有的視圖進行布局,因為,最簡單的想法就是只要設置一個標識就好。
首先給mPrivateFlags添加FORCE_LAYOUT標識,然后調用mParent的requestLayout()函數。
1、調用checkThread()確保本次調用是在UI線程中執行的,非UI線程執行該函數將導致狀態管理的混亂,并最終crash掉。
2、給ViewRoot中的變量mLayoutRequested賦值為true,之后真正進行布局的代碼將檢查該變量,并決定是否需要重新布局。
3、調用scheduleTraversals()發起一個View樹遍歷的消息,該消息是異步處理的,對應的處理函數是performTraversals()。