@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); } elseif (mParent != null) { return mParent.focusSearch(focused, direction); } returnnull; }
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { if (shouldBlockFocusForTouchscreen()) { focusableMode |= FOCUSABLES_TOUCH_MODE; }
finalint 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); } }
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); } } }
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: thrownew IllegalArgumentException("Unknown direction: " + direction); } }
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);
booleanisBetterCandidate(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)) { returnfalse; }
// we know that rect1 is a candidate.. if rect2 is not a candidate, // rect1 is better if (!isCandidate(source, rect2, direction)) { returntrue; }
// if rect1 is better by beam, it wins if (beamBeats(direction, source, rect1, rect2)) { returntrue; }
// if rect2 is better, then rect1 cant' be :) if (beamBeats(direction, source, rect2, rect1)) { returnfalse; }
// 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))); }
booleanbeamBeats(int direction, Rect source, Rect rect1, Rect rect2){ finalboolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); finalboolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
// if rect1 isn't exclusively in the src beam, it doesn't win if (rect2InSrcBeam || !rect1InSrcBeam) { returnfalse; }
// 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)) { returntrue; }
// for horizontal directions, being exclusively in beam always wins if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) { returntrue; }
// 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)); }
@Override public View focusSearch(View focused, int direction){ View result = mLayout.onInterceptFocusSearch(focused, direction); if (result != null) { return result; } finalboolean 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()) { finalint 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; finalint 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. returnnull; } 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. returnnull; } 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. returnsuper.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); }