if (focused == null || focused == root) {
return root;
}
- ViewParent effective = focused.getParent();
+ ViewGroup effective = null;
+ ViewParent nextParent = focused.getParent();
do {
- if (effective == root) {
- return root;
+ if (nextParent == root) {
+ return effective != null ? effective : root;
}
- ViewGroup vg = (ViewGroup) effective;
+ ViewGroup vg = (ViewGroup) nextParent;
if (vg.getTouchscreenBlocksFocus()
&& focused.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN)
&& vg.isKeyboardNavigationCluster()) {
- return vg;
+ // Don't stop and return here because the cluster could be nested and we only
+ // care about the top-most one.
+ effective = vg;
}
- effective = effective.getParent();
- } while (effective != null);
+ nextParent = nextParent.getParent();
+ } while (nextParent instanceof ViewGroup);
return root;
}
}
/**
+ * Searches up the view hierarchy to find the top-most cluster. All deeper/nested clusters
+ * will be ignored.
+ *
+ * @return the keyboard navigation cluster that this view is in (can be this view)
+ * or {@code null} if not in one
+ */
+ View findKeyboardNavigationCluster() {
+ if (mParent instanceof View) {
+ View cluster = ((View) mParent).findKeyboardNavigationCluster();
+ if (cluster != null) {
+ return cluster;
+ } else if (isKeyboardNavigationCluster()) {
+ return this;
+ }
+ }
+ return null;
+ }
+
+ /**
* Set whether this view is a root of a keyboard navigation cluster.
*
* @param isCluster If true, this view is a root of a cluster.
*
* @hide
*/
- public void setFocusedInCluster() {
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).setFocusInCluster(this);
+ public final void setFocusedInCluster() {
+ View top = findKeyboardNavigationCluster();
+ if (top == this) {
+ return;
+ }
+ ViewParent parent = mParent;
+ View child = this;
+ while (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).setFocusedInCluster(child);
+ if (parent == top) {
+ return;
+ }
+ child = (View) parent;
+ parent = parent.getParent();
}
}
return mDefaultFocus != null || super.hasDefaultFocus();
}
- void setFocusInCluster(View child) {
- // Stop at the root of the cluster
- if (child.isKeyboardNavigationCluster()) {
- return;
- }
-
+ void setFocusedInCluster(View child) {
mFocusedInCluster = child;
-
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).setFocusInCluster(this);
- }
}
- void clearFocusInCluster(View child) {
+ /**
+ * Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
+ * it.
+ * <br>
+ * This is intended to be run on {@code child}'s immediate parent. This is necessary because
+ * the chain is sometimes cleared after {@code child} has been detached.
+ */
+ void clearFocusedInCluster(View child) {
if (mFocusedInCluster != child) {
return;
}
-
- if (child.isKeyboardNavigationCluster()) {
- return;
- }
-
- mFocusedInCluster = null;
-
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).clearFocusInCluster(this);
- }
+ View top = findKeyboardNavigationCluster();
+ ViewParent parent = this;
+ do {
+ ((ViewGroup) parent).mFocusedInCluster = null;
+ parent = parent.getParent();
+ } while (parent != top && parent instanceof ViewGroup);
}
@Override
public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
if (touchscreenBlocksFocus) {
mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
- if (hasFocus()) {
+ if (hasFocus() && !isKeyboardNavigationCluster()) {
final View focusedChild = getDeepestFocusedChild();
if (!focusedChild.isFocusableInTouchMode()) {
final View newFocus = focusSearch(FOCUS_FORWARD);
// cluster, focus is free to move around within it.
return getTouchscreenBlocksFocus() &&
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
- && (!hasFocus() || !isKeyboardNavigationCluster());
+ && !(isKeyboardNavigationCluster()
+ && (hasFocus() || (findKeyboardNavigationCluster() != this)));
}
@Override
}
private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
- if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
- && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+ if (mFocusedInCluster != null && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
&& (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
&& mFocusedInCluster.restoreFocusInCluster(direction)) {
return true;
clearChildFocus = true;
}
if (view == mFocusedInCluster) {
- clearFocusInCluster(view);
+ clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
clearDefaultFocus = view;
}
if (view == mFocusedInCluster) {
- clearFocusInCluster(view);
+ clearFocusedInCluster(view);
}
view.clearAccessibilityFocus();
clearDefaultFocus(child);
}
if (child == mFocusedInCluster) {
- clearFocusInCluster(child);
+ clearFocusedInCluster(child);
}
child.clearAccessibilityFocus();