天天看點

[Android]Android焦點流程代碼分析

以下内容為原創,歡迎轉載,轉載請注明

通過View的<code>View::focusSearch</code>進行焦點搜尋對應方向上的下一個可以擷取焦點的View:

不斷地調用父控件來進行搜尋,focusSearch有兩個實作:<code>ViewGroup</code>和<code>RecyclerView</code>,先看<code>ViewGroup</code>:

如果是最頂層,則直接調用<code>FocusFinder::findNextFocus</code>方法進行搜尋;否則調用父控件的<code>focusSearch</code>。<code>FocusFinder::findNextFocus</code>如下:

上面的<code>root</code>參數代表的是最頂層的view。

首先,通過嘗試通過<code>findNextUserSpecifiedFocus</code>來查找下一個“指定的”可獲得焦點的View,這個指定是開發者通過SDK自帶的<code>setNextFocusLeftId</code>等方法進行手動設定的。如果查找到指定的下一個可獲得焦點的View,則傳回該View;否則,執行<code>View::addFocusables</code>方法,通過這個最頂層的View去拿到所有直接或間接的<code>Focusable</code>的子View,并添加到<code>ArrayList&lt;View&gt; focusables</code>中。

<code>View::addFolcusables</code>方法中有4種實作:

第一種是View中預設實作:

如果自己是focusable的話,直接把自己添加進去。

第二種是<code>ViewGroup</code>實作:

先會處理自身ViewGroup與它後代的關系(descendantFocusability),前面提到過,可能的幾種情況: FOCUS_BEFORE_DESCENDANTS: ViewGroup本身先對焦點進行處理,如果沒有處理則分發給child View進行處理 FOCUS_AFTER_DESCENDANTS: 先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理 FOCUS_BLOCK_DESCENDANTS: ViewGroup本身進行處理,不管是否處理成功,都不會分發給ChildView進行處理

是以,以上:

如果不是<code>FOCUS_BLOCK_DESCENDANTS</code>,則首先檢查<code>blockForTouchscreen</code>并重置掉<code>focusableMode</code>,然後周遊所有的子View,調用<code>child::addFocusables</code>。

如果不是<code>FOCUS_AFTER_DESCENDANTS</code>或者沒有focusable的子View時自己處理,是以把自己加入到<code>views</code>中。

第三種是<code>ViewPager</code>實作:

與<code>ViewGroup</code>基本一緻,在<code>descendantFocusability</code>不是<code>FOCUS_BLOCK_DESCENDANTS</code>時,周遊子View時判斷view是否屬于目前的page,如果是才加進去。如果不是<code>FOCUS_AFTER_DESCENDANTS</code>或者沒有focusable的子View時自己處理,是以把自己加入到<code>views</code>中。

第四種是<code>RecyclerView</code>實作:

通過<code>LayoutManager::onAddFocusables</code>來進行管理,如果傳回false,則直接調用父類<code>ViewGroup</code>的方法,看下<code>LayoutManager::onAddFocusables</code>的實作:

直接傳回false,并且沒有看到相關的<code>LayoutManager</code>對該方法的重寫,是以,這裡應該是直接調用了父類<code>ViewGroup</code>的方法。

是以重點是<code>FocusFinder::findNextFocus</code>方法的實作:

如果focused不是null,說明目前擷取到焦點的View存在,則獲得繪制焦點的Rect到focusedRect,然後根據rootView周遊所有ParentView從子View糾正坐标到根View坐标。

如果focused是null,則說明目前沒有View擷取到焦點,則把focusedRect根據不同的direction重置為“一點”。

最後根據direction調用<code>FocusFinder::findNextFocusInAbsoluteDirection</code>方法進行對比查找“下一個”View。

首先把最優選擇mBestCandidateRect設定為focusedRect,根據方向做1像素的偏移便于對比。周遊所有剛剛查詢出來的<code>focusables</code>,拿到每一個的<code>focusedRect</code>區域并進行轉換,然後通過<code>FocusFinder::isBetterCandidate</code>方法進行對比,然後拿到更好的,周遊完成後就是最優選擇。接下來看下<code>FocusFinder::isBetterCandidate</code>方法來了解下是怎麼做對比的:

下面代碼意思是:以source這個rect來說,作為對應derection上下一個focus view,rect1是否比rect2更優?

首先确定rect1是否<code>isCandidate</code>?<code>isCandidate</code>做的邏輯簡單來說就是确定rect是否滿足給定的derection作為下一個focus view這個條件,它的判斷依據如下:

如果rect1不滿足基本條件,則肯定傳回false(基本的條件都不滿足)

如果rect2不滿足基本條件,則傳回true,認為rect1更優

如果都滿足基本條件的情況下,通過<code>FocusFinder::beamBeats</code>方法來判斷哪種更優

接下來看下<code>FocusFinder::beamBeats</code>的實作:

首先通過<code>beamsOverlap</code>方法來判斷兩個rect與source是否重疊等等。注意的是,在水準情況下,如果rect1重疊,則就是最優解(為什麼?比較奇怪),最後如果是豎直情況,通過<code>FocusFinder::majorAxisDistance</code>方法來判斷哪個離source最近。如果還是比較不出,則通過<code>getWeightedDistanceFor</code>方法來通過“主要距離”和“次要距離”做一個綜合的比較。

首先通過<code>onInterceptFocusSearch</code>進行攔截,如果傳回具體的focus View,則直接傳回;否則繼續往下;<code>onInterceptFocusSearch</code>實作如下:

預設為空實作,傳回null,也沒有其它的子類進行重寫,是以暫時不管這個處理,繼續看<code>focusSearch</code>:

我們暫時隻考慮<code>direction</code>為left,top,right,down的情況,則進入最外面if的else分支:

繼續閱讀