Android View焦點(diǎn)總結(jié)
Android View焦點(diǎn)
Android焦點(diǎn)相關(guān)邏輯大部分都在都在View, ViewGroup和FocusFinder三個(gè)類中.
ViewRoot
View對(duì)象都有一個(gè)mParent變量(添加到ViewGroup后), 代指其父容器. 絕大部分View的mParent都是ViewGroup類型, 除了根節(jié)點(diǎn). 一個(gè)Window中View根節(jié)點(diǎn)DecorView的mParent稱為ViewRoot, 在安卓4.0后ViewRoot對(duì)應(yīng)ViewRootImpl, 它不是View的子類, 而是個(gè)ViewParent. ViewRootImpl是連接Window和DecorView的紐帶, View的焦點(diǎn), 按鍵, 布局, 渲染等流程都是從ViewRoot中開始的.
View的焦點(diǎn)
基本流程如下
View(包括ViewGroup)獲取焦點(diǎn)都通過(guò)如下三個(gè)方法
View.java
從上面可以看到前兩個(gè)最終會(huì)執(zhí)行到第三個(gè)方法.
***的requestFocusNoSearch先判斷是否可以獲取焦點(diǎn), 然后進(jìn)入下面的***流程:
View.java
上面的流程比較簡(jiǎn)單: 如果當(dāng)前沒(méi)有焦點(diǎn), 先置焦點(diǎn)標(biāo)志, 再通知parent, 然后刷新圖片.
主要的流程在mParent的requestChildFocus里面, 后面會(huì)分析. 那里會(huì)逐層向上修改焦點(diǎn)View并清除原來(lái)有焦點(diǎn)的View的焦點(diǎn)
onFocusChange會(huì)觸發(fā)invalidate刷新, 然后調(diào)用onFocusChangeListener. 默認(rèn)情況每個(gè)View只能設(shè)置一個(gè)onFocusChangeListener, 而開發(fā)中經(jīng)常遇到需要設(shè)置多個(gè)Listener的情況, 我們就可以重寫onFocusChange方法, 實(shí)現(xiàn)回調(diào)多個(gè)onFocusChangeListener的需求.
ViewGroup的焦點(diǎn)
ViewGroup獲取焦點(diǎn)是在View獲取焦點(diǎn)流程中多了內(nèi)部焦點(diǎn)處理
ViewGroup.java
上面代碼中descendantFocusability決定了是先按View焦點(diǎn)流程處理(自己處理焦點(diǎn))還是先把給子View處理
FOCUS_BLOCK_DESCENDANTS 不允許子View獲取焦點(diǎn), 那么按照View的流程進(jìn)行
FOCUS_BEFORE_DESCENDANTS 先按照View的流程處理, 如果自己不能獲取焦點(diǎn)則給孩子處理
FOCUS_AFTER_DESCENDANTS 先嘗試給孩子焦點(diǎn), 如果沒(méi)有可獲取焦點(diǎn)再按照View流程自己獲取焦點(diǎn)
默認(rèn)值FOCUS_BEFORE_DESCENDANTS, 我們可以通過(guò)setDescendantFocusability(int d)
設(shè)置
onRequestFocusInDescendants方法是給子類重寫使用, 可以控制子View處理焦點(diǎn). 默認(rèn)按照子View順序處理, direction向下或向右則從***個(gè)開始, 向上或向左則從***一個(gè)開始, 直到某個(gè)子View獲取焦點(diǎn)
注意此方法只在此ViewGroup及其上層View上調(diào)用requestFocus時(shí)會(huì)執(zhí)行到
父容器焦點(diǎn)的處理
在View獲取焦點(diǎn)流程中會(huì)調(diào)用mParent.requestChildFocus, 維護(hù)View樹上焦點(diǎn)唯一, 在各層ViewGroup中保存有焦點(diǎn)的子View
ViewGroup.java
先清除自己的焦點(diǎn), 如果原來(lái)內(nèi)部有焦點(diǎn), 先清除其焦點(diǎn), 保存獲取焦點(diǎn)的孩子, 然后調(diào)用上一層的requestChildFocus. ***的調(diào)用可知, 這個(gè)方法會(huì)一直調(diào)用到View的樹的root節(jié)點(diǎn).
在當(dāng)前ViewGroup內(nèi)部, 任何一個(gè)孩子取得焦點(diǎn)都會(huì)執(zhí)行到這個(gè)方法, 因此此方法也是ViewGroup得知孩子焦點(diǎn)變化的方法之一.(可惜不能得知孩子失去焦點(diǎn))
失去焦點(diǎn)或清除焦點(diǎn)
獲取焦點(diǎn)可以是主動(dòng)的, 但失去焦點(diǎn)一般都是被動(dòng)的(見上面的代碼), 因此邏輯相對(duì)簡(jiǎn)單, 只要清除焦點(diǎn)狀態(tài)即可.
ViewGroup.java
View.java
注意上面的方法是默認(rèn)package訪問(wèn)級(jí)別的, 我們無(wú)法重寫也不能調(diào)用
也可以主動(dòng)清除焦點(diǎn), 與獲取焦點(diǎn)流程相似
ViewGroup.java
View.java
ViewGroup.java
以上是安卓View系統(tǒng)焦點(diǎn)處理的全部流程和涉及到的方法, ViewRootImpl的requestChildFocus和clearChildFocus實(shí)現(xiàn)我們不需要關(guān)注
另外還有以下一些輔助方法
boolean isFocusable() View是否可以獲取焦點(diǎn)
boolean isFocused() View是否獲取焦點(diǎn)
boolean hasFocus() View/ViewGroup內(nèi)部是否有焦點(diǎn)
View findFocus() 取到View/ViewGroup內(nèi)部的焦點(diǎn)View
View getFocusedChild() 取到ViewGroup內(nèi)部有焦點(diǎn)的子View
View getRootView() 取到根節(jié)點(diǎn)View(一般是DecorView或頂層ViewGroup)
焦點(diǎn)移動(dòng)
除了在代碼里面控制焦點(diǎn), 系統(tǒng)對(duì)沒(méi)有處理的方向鍵等一些按鍵自動(dòng)按照焦點(diǎn)移動(dòng)來(lái)處理, 見下面代碼
ViewRootImpl.java
代碼比較上, 但是主要做了三個(gè)步驟
如果View沒(méi)有處理按鍵, 把上下左右tab等按鍵轉(zhuǎn)換成對(duì)應(yīng)方向
在當(dāng)前焦點(diǎn)View上通過(guò)focusSearch方法查找對(duì)應(yīng)方向的下一個(gè)View
查找到的View調(diào)用requestFocus因此主要的流程在focusSearch中
View.java
普通View查找什么都沒(méi)做, 交給parent來(lái)完成.
ViewGroup.java
ViewRootImpl
我們可以重寫focusSearch控制焦點(diǎn)移動(dòng)順序, 而默認(rèn)的焦點(diǎn)移動(dòng)順序由FocusFinder決定
FocusFinder查找焦點(diǎn)
FocusFinder為public的工具類, 主要就兩個(gè)方法, 可以在給定的View內(nèi)在指定方向查找指定View或坐標(biāo)的下一個(gè)焦點(diǎn)如下:
核心邏輯就兩步, 先查找setNextFocusXXId設(shè)置的View, 如果沒(méi)有按照就近算法查找.具體算法不再分析, SDK里面有源碼.
總結(jié)
綜合上面的流程分析, 我們?cè)趯?shí)現(xiàn)自定義View時(shí), 對(duì)焦點(diǎn)的特殊需求有如下思路
requestFocus和clearFocus直接對(duì)View清除或轉(zhuǎn)移焦點(diǎn)
除了onFocusChangeListener, 還可以在onFocusChange方法中實(shí)現(xiàn)一些View失去/獲得焦點(diǎn)時(shí)通知
對(duì)ViewGroup, 如果只需要在子View獲取焦點(diǎn)時(shí)得到通知, 有requestChildFocus方法.
重寫onRequestFocusInDescendants方法可以控制某些情景下ViewGroup焦點(diǎn)
控制焦點(diǎn)移動(dòng)可以重寫focusSearch方法
另外還有FocusFinder工具和上面的輔助方法.