以下內容為原創,歡迎轉載,轉載請注明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7286503.html
通過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`:
@Override
public 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<View> 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<View> focusables
中。
View::addFolcusables
方法中有4種實現:
第一種是View中默認實現:
public void addFocusables(ArrayList<View> 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
實現:
@Override
public void addFocusables(ArrayList<View> 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
實現:
@Override
public void addFocusables(ArrayList<View> 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
實現:
@Override
public void addFocusables(ArrayList<View> 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<View> 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<View> 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<View> 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
的實現:
@Override
public 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
:
@Override
public 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 邏輯一致。