以下內容為原創, 歡迎轉載, 轉載請注明
通過View的View::focusSearch進行焦點搜索對應方向上的下一個可以獲取焦點的View:
public View focusSearch(@FocusRealDirection int direction) { if (mParent != null) { return mParent.focusSearch(this, direction); } else { return null; }}不斷地調用父控制項來進行搜索, focusSearch有兩個實現:ViewGroup和RecyclerView, 先看ViewGroup:
@Overridepublic View focusSearch(View focused, int direction) { if (isRootNamespace) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching // into other tabs. see LocalActivityManager and TabHost for more info return FocusFinder.getInstance.findNextFocus(this, focused, direction); } else if (mParent != null) { return mParent.focusSearch(focused, direction); } return null;}如果是最頂層, 則直接調用FocusFinder::findNextFocus方法進行搜索;否則調用父控制項的focusSearch。 FocusFinder::findNextFocus如下:
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { View next = null; if (focused != null) { next = findNextUserSpecifiedFocus(root, focused, direction); } if (next != null) { return next; } ArrayList上面的root參數代表的是最頂層的view。
首先,
通過嘗試通過findNextUserSpecifiedFocus來查找下一個“指定的”可獲得焦點的View,
這個指定是開發者通過SDK自帶的setNextFocusLeftId等方法進行手動設置的。
如果查找到指定的下一個可獲得焦點的View,
則返回該View;否則,
執行View::addFocusables方法,
通過這個最頂層的View去拿到所有直接或間接的Focusable的子View,
並添加到ArrayList
View::addFolcusables方法中有4種實現:
第一種是View中默認實現:
public void addFocusables(ArrayList如果自己是focusable的話, 直接把自己添加進去。
第二種是ViewGroup實現:
@Overridepublic void addFocusables(ArrayList先會處理自身ViewGroup與它後代的關係(descendantFocusability), 前面提到過, 可能的幾種情況:
FOCUS_BEFORE_DESCENDANTS: ViewGroup本身先對焦點進行處理, 如果沒有處理則分發給child View進行處理FOCUS_AFTER_DESCENDANTS: 先分發給Child View進行處理, 如果所有的Child View都沒有處理, 則自己再處理FOCUS_BLOCK_DESCENDANTS: ViewGroup本身進行處理, 不管是否處理成功, 都不會分發給ChildView進行處理所以, 以上:
如果不是FOCUS_BLOCK_DESCENDANTS, 則首先檢查blockForTouchscreen並重置掉focusableMode, 然後遍歷所有的子View, 調用child::addFocusables。 如果不是FOCUS_AFTER_DESCENDANTS或者沒有focusable的子View時自己處理, 所以把自己加入到views中。第三種是ViewPager實現:
@Overridepublic void addFocusables(ArrayList與ViewGroup基本一致, 在descendantFocusability不是FOCUS_BLOCK_DESCENDANTS時, 遍歷子View時判斷view是否屬於當前的page, 如果是才加進去。 如果不是FOCUS_AFTER_DESCENDANTS或者沒有focusable的子View時自己處理, 所以把自己加入到views中。
第四種是RecyclerView實現:
通過LayoutManager::onAddFocusables來進行管理, 如果返回false, 則直接調用父類ViewGroup的方法, 看下LayoutManager::onAddFocusables的實現:
public boolean onAddFocusables(RecyclerView recyclerView, ArrayList直接返回false, 並且沒有看到相關的LayoutManager對該方法的重寫, 所以, 這裡應該是直接調用了父類ViewGroup的方法。
addFocusables方法完畢, 回到FocusFinder::findNextFocus方法中通過root.addFocusables(focusables, direction);加入所有可獲取焦點的View之後, 在非空的情況下調用如下代碼:
next = findNextFocus(root, focused, focusedRect, direction, focusables);所以重點是FocusFinder::findNextFocus方法的實現:
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList最後根據direction調用FocusFinder::findNextFocusInAbsoluteDirection方法進行對比查找“下一個”View。
View findNextFocusInAbsoluteDirection(ArrayList首先把最優選擇mBestCandidateRect設置為focusedRect, 根據方向做1圖元的偏移便於對比。 遍歷所有剛剛查詢出來的focusables, 拿到每一個的focusedRect區域並進行轉換, 然後通過FocusFinder::isBetterCandidate方法進行對比,
下面代碼意思是:以source這個rect來說, 作為對應derection上下一個focus view, rect1是否比rect2更優?
boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) { // to be a better candidate, need to at least be a candidate in the first // place :) if (!isCandidate(source, rect1, direction)) { return false; } // we know that rect1 is a candidate.. if rect2 is not a candidate, // rect1 is better if (!isCandidate(source, rect2, direction)) { return true; } // if rect1 is better by beam, it wins if (beamBeats(direction, source, rect1, rect2)) { return true; } // if rect2 is better, then rect1 cant' be :) if (beamBeats(direction, source, rect2, rect1)) { return false; } // otherwise, do fudge-tastic comparison of the major and minor axis return (getWeightedDistanceFor( majorAxisDistance(direction, source, rect1), minorAxisDistance(direction, source, rect1)) < getWeightedDistanceFor( majorAxisDistance(direction, source , rect2), minorAxisDistance(direction, source, rect2)));}首先確定rect1是否isCandidate?isCandidate做的邏輯簡單來說就是確定rect是否滿足給定的derection作為下一個focus view這個條件, 它的判斷依據如下:
boolean isCandidate(Rect srcRect, Rect destRect, int direction) { switch (direction) { case View.FOCUS_LEFT: return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left; case View.FOCUS_RIGHT: return (srcRect.left < destRect.left || srcRect.right <= destRect.left) && srcRect.right < destRect.right; case View.FOCUS_UP: return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) && srcRect.top > destRect.top; case View.FOCUS_DOWN: return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) && srcRect.bottom < destRect.bottom; } throw new IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}代碼比較簡單。 再回到 FocusFinder::isBetterCandidate的代碼邏輯:
如果rect1不滿足基本條件, 則肯定返回false(基本的條件都不滿足)如果rect2不滿足基本條件, 則返回true, 認為rect1更優如果都滿足基本條件的情況下, 通過FocusFinder::beamBeats方法來判斷哪種更優接下來看下FocusFinder::beamBeats的實現:
boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) { final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2); // if rect1 isn't exclusively in the src beam, it doesn't win if (rect2InSrcBeam || !rect1InSrcBeam) { return false; } // we know rect1 is in the beam, and rect2 is not // if rect1 is to the direction of, and rect2 is not, rect1 wins. // for example, for direction left, if rect1 is to the left of the source // and rect2 is below, then we always prefer the in beam rect1, since rect2 // could be reached by going down. if (!isToDirectionOf(direction, source, rect2)) { return true; } // for horizontal directions, being exclusively in beam always wins if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) { return true; } // for vertical directions, beams only beat up to a point: // now, as long as rect2 isn't completely closer, rect1 wins // e.g for direction down, completely closer means for rect2's top // edge to be closer to the source's top edge than rect1's bottom edge. return (majorAxisDistance(direction, source, rect1) < majorAxisDistanceToFarEdge(direction, source, rect2));}首先通過beamsOverlap方法來判斷兩個rect與source是否重疊等等。 注意的是, 在水準情況下, 如果rect1重疊, 則就是最優解(為什麼?比較奇怪), 最後如果是豎直情況,
繼續 focusSearch代碼的分析, 剛剛只跟了ViewGroup, 還有一個實現是RecyclerView的實現:
@Overridepublic View focusSearch(View focused, int direction) { View result = mLayout.onInterceptFocusSearch(focused, direction); if (result != null) { return result; } // ...}首先通過onInterceptFocusSearch進行攔截, 如果返回具體的focus View, 則直接返回;否則繼續往下;onInterceptFocusSearch實現如下:
public View onInterceptFocusSearch(View focused, int direction) { return null;}默認為空實現, 返回null, 也沒有其它的子類進行重寫, 所以暫時不管這個處理, 繼續看focusSearch:
@Overridepublic View focusSearch(View focused, int direction) { View result = mLayout.onInterceptFocusSearch(focused, direction); if (result != null) { return result; } final boolean canRunFocusFailure = mAdapter != null && mLayout != null && !isComputingLayout && !mLayoutFrozen; final FocusFinder ff = FocusFinder.getInstance; if (canRunFocusFailure && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { // convert direction to absolute direction and see if we have a view there and if not // tell LayoutManager to add if it can. boolean needsFocusFailureLayout = false; if (mLayout.canScrollVertically) { final int absDir = direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; final View found = ff.findNextFocus(this, focused, absDir); needsFocusFailureLayout = found == null; if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. direction = absDir; } } if (!needsFocusFailureLayout && mLayout.canScrollHorizontally) { boolean rtl = mLayout.getLayoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL; final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT; final View found = ff.findNextFocus(this, focused, absDir); needsFocusFailureLayout = found == null; if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. direction = absDir; } } if (needsFocusFailureLayout) { consumePendingUpdateOperations; final View focusedItemView = findContainingItemView(focused); if (focusedItemView == null) { // panic, focused view is not a child anymore, cannot call super. return null; } eatRequestLayout; mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); resumeRequestLayout(false); } result = ff.findNextFocus(this, focused, direction); } else { result = ff.findNextFocus(this, focused, direction); if (result == null && canRunFocusFailure) { consumePendingUpdateOperations; final View focusedItemView = findContainingItemView(focused); if (focusedItemView == null) { // panic, focused view is not a child anymore, cannot call super. return null; } eatRequestLayout; result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); resumeRequestLayout(false); } } if (result != null && !result.hasFocusable) { if (getFocusedChild == null) { // Scrolling to this unfocusable view is not meaningful since there is no currently // focused view which RV needs to keep visible. return super.focusSearch(focused, direction); } // If the next view returned by onFocusSearchFailed in layout manager has no focusable // views, we still scroll to that view in order to make it visible on the screen. // If it's focusable, framework already calls RV's requestChildFocus which handles // bringing this newly focused item onto the screen. requestChildOnScreen(result, null); return focused; } return isPreferredNextFocus(focused, result, direction) ? result : super.focusSearch(focused, direction);}我們暫時只考慮direction為left, top, right, down的情況, 則進入最外面if的else分支:
public View focusSearch(View focused, int direction) { // ... result = ff.findNextFocus(this, focused, direction); if (result == null && canRunFocusFailure) { consumePendingUpdateOperations; final View focusedItemView = findContainingItemView(focused); if (focusedItemView == null) { // panic, focused view is not a child anymore, cannot call super. return null; } eatRequestLayout; result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); resumeRequestLayout(false); } // ...}首先通過FocusFinder::findNextFocus方法來獲取下一個應該獲得焦點的View, 這裡獲取的結果與FocusFinder::findNextFocus邏輯一致。