您的位置:首頁>正文

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

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

通過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 focusables = mTempList; try { focusables.clear; root.addFocusables(focusables, direction); if (!focusables.isEmpty) { next = findNextFocus(root, focused, focusedRect, direction, focusables); } } finally { focusables.clear; } return next;}

上面的root參數代表的是最頂層的view。

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

View::addFolcusables方法中有4種實現:

第一種是View中默認實現:

public void addFocusables(ArrayList views, @FocusDirection int direction, @FocusableMode int focusableMode) { if (views == null) { return; } if (!isFocusable) { return; } if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && !isFocusableInTouchMode) { return; } views.add(this); }

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

第二種是ViewGroup實現:

@Overridepublic void addFocusables(ArrayList views, int direction, int focusableMode) { final int focusableCount = views.size; final int descendantFocusability = getDescendantFocusability; if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { if (shouldBlockFocusForTouchscreen) { focusableMode |= FOCUSABLES_TOUCH_MODE; } final int count = mChildrenCount; final View children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.addFocusables(views, direction, focusableMode); } } } // we add ourselves (if focusable) in all cases except for when we are // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is // to avoid the focus search finding layouts when a more precise search // among the focusable children would be more interesting. if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS // No focusable descendants || (focusableCount == views.size)) && (isFocusableInTouchMode || !shouldBlockFocusForTouchscreen)) { super.addFocusables(views, direction, focusableMode); }}

先會處理自身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 views, int direction, int focusableMode) { final int focusableCount = views.size; final int descendantFocusability = getDescendantFocusability; if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { for (int i = 0; i < getChildCount; i++) { final View child = getChildAt(i); if (child.getVisibility == VISIBLE) { ItemInfo ii = infoForChild(child); if (ii != null && ii.position == mCurItem) { child.addFocusables(views, direction, focusableMode); } } } } // we add ourselves (if focusable) in all cases except for when we are // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is // to avoid the focus search finding layouts when a more precise search // among the focusable children would be more interesting. if (descendantFocusability != FOCUS_AFTER_DESCENDANTS || (focusableCount == views.size)) { // No focusable descendants // Note that we can't call the superclass here, because it will // add all views in. So we need to do the same thing View does. if (!isFocusable) { return; } if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && isInTouchMode && !isFocusableInTouchMode) { return; } if (views != null) { views.add(this); } }}

與ViewGroup基本一致, 在descendantFocusability不是FOCUS_BLOCK_DESCENDANTS時, 遍歷子View時判斷view是否屬於當前的page, 如果是才加進去。 如果不是FOCUS_AFTER_DESCENDANTS或者沒有focusable的子View時自己處理, 所以把自己加入到views中。

第四種是RecyclerView實現:

@Overridepublic void addFocusables(ArrayList views, int direction, int focusableMode) { if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { super.addFocusables(views, direction, focusableMode); }}

通過LayoutManager::onAddFocusables來進行管理, 如果返回false, 則直接調用父類ViewGroup的方法, 看下LayoutManager::onAddFocusables的實現:

public boolean onAddFocusables(RecyclerView recyclerView, ArrayList views, int direction, int focusableMode) { return false;}

直接返回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 focusables) { if (focused != null) { if (focusedRect == null) { focusedRect = mFocusedRect; } // fill in interesting rect from focused focused.getFocusedRect(focusedRect); root.offsetDescendantRectToMyCoords(focused, focusedRect); } else { if (focusedRect == null) { focusedRect = mFocusedRect; // make up a rect at top left or bottom right of root switch (direction) { case View.FOCUS_RIGHT: case View.FOCUS_DOWN: setFocusTopLeft(root, focusedRect); break; case View.FOCUS_FORWARD: if (root.isLayoutRtl) { setFocusBottomRight(root, focusedRect); } else { setFocusTopLeft(root, focusedRect); } break; case View.FOCUS_LEFT: case View.FOCUS_UP: setFocusBottomRight(root, focusedRect); break; case View.FOCUS_BACKWARD: if (root.isLayoutRtl) { setFocusTopLeft(root, focusedRect); } else { setFocusBottomRight(root, focusedRect); break; } } } } switch (direction) { case View.FOCUS_FORWARD: case View.FOCUS_BACKWARD: return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect, direction); case View.FOCUS_UP: case View.FOCUS_DOWN: case View.FOCUS_LEFT: case View.FOCUS_RIGHT: return findNextFocusInAbsoluteDirection(focusables, root, focused, focusedRect, direction); default: throw new IllegalArgumentException("Unknown direction: " + direction); }} 如果focused不是null, 說明當前獲取到焦點的View存在, 則獲得繪製焦點的Rect到focusedRect, 然後根據rootView遍歷所有ParentView從子View糾正座標到根View座標。 如果focused是null, 則說明當前沒有View獲取到焦點, 則把focusedRect根據不同的direction重置為“一點”。

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

View findNextFocusInAbsoluteDirection(ArrayList focusables, ViewGroup root, View focused, Rect focusedRect, int direction) { // initialize the best candidate to something impossible // (so the first plausible view will become the best choice) mBestCandidateRect.set(focusedRect); switch(direction) { case View.FOCUS_LEFT: mBestCandidateRect.offset(focusedRect.width + 1, 0); break; case View.FOCUS_RIGHT: mBestCandidateRect.offset(-(focusedRect.width + 1), 0); break; case View.FOCUS_UP: mBestCandidateRect.offset(0, focusedRect.height + 1); break; case View.FOCUS_DOWN: mBestCandidateRect.offset(0, -(focusedRect.height + 1)); } View closest = null; int numFocusables = focusables.size; for (int i = 0; i < numFocusables; i++) { View focusable = focusables.get(i); // only interested in other non-root views if (focusable == focused || focusable == root) continue; // get focus bounds of other view in same coordinate system focusable.getFocusedRect(mOtherRect); root.offsetDescendantRectToMyCoords(focusable, mOtherRect); if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) { mBestCandidateRect.set(mOtherRect); closest = focusable; } } return closest;}

首先把最優選擇mBestCandidateRect設置為focusedRect, 根據方向做1圖元的偏移便於對比。 遍歷所有剛剛查詢出來的focusables, 拿到每一個的focusedRect區域並進行轉換, 然後通過FocusFinder::isBetterCandidate方法進行對比,

然後拿到更好的, 遍歷完成後就是最優選擇。 接下來看下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重疊, 則就是最優解(為什麼?比較奇怪), 最後如果是豎直情況,

通過FocusFinder::majorAxisDistance方法來判斷哪個離source最近。 如果還是比較不出, 則通過getWeightedDistanceFor方法來通過“主要距離”和“次要距離”做一個綜合的比較。

RecyclerView

繼續 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邏輯一致。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示