if (mParent != null) {
mParent.requestChildFocus(this, this);
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).setDefaultFocus(this);
- }
+ setFocusedInCluster();
}
if (mAttachInfo != null) {
}
/**
+ * Sets this View as the one which receives focus the next time cluster navigation jumps
+ * to the cluster containing this View. This does NOT change focus even if the cluster
+ * containing this view is current.
+ *
+ * @hide
+ */
+ public void setFocusedInCluster() {
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).setFocusInCluster(this);
+ }
+ }
+
+ /**
* Returns whether this View should receive focus when the focus is restored for the view
* hierarchy containing this view.
* <p>
if (isFocusedByDefault) {
((ViewGroup) mParent).setDefaultFocus(this);
} else {
- ((ViewGroup) mParent).cleanDefaultFocus(this);
+ ((ViewGroup) mParent).clearDefaultFocus(this);
}
}
}
}
/**
+ * Public for testing. This will request focus for whichever View was last focused within this
+ * cluster before a focus-jump out of it.
+ *
+ * @hide
+ */
+ public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+ // Prioritize focusableByDefault over algorithmic focus selection.
+ if (restoreDefaultFocus()) {
+ return true;
+ }
+ return requestFocus(direction);
+ }
+
+ /**
* Gives focus to the default-focus view in the view hierarchy that has this view as a root.
* If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
- * Nested keyboard navigation clusters are excluded from the hierarchy.
*
- * @param direction The direction of the focus
* @return Whether this view or one of its descendants actually took focus
*/
- public boolean restoreDefaultFocus(@FocusDirection int direction) {
- return requestFocus(direction);
+ public boolean restoreDefaultFocus() {
+ return requestFocus(View.FOCUS_DOWN);
}
/**
// The view contained within this ViewGroup (excluding nested keyboard navigation clusters)
// that is or contains a default-focus view.
private View mDefaultFocus;
+ // The last child of this ViewGroup which held focus within the current cluster
+ private View mFocusedInCluster;
/**
* A Transformation used when drawing children, to
if (mFocused != null) {
mFocused.unFocus(this);
mFocused = null;
- mDefaultFocus = null;
+ mFocusedInCluster = null;
}
super.handleFocusGainInternal(direction, previouslyFocusedRect);
}
}
}
- /**
- * Sets the specified child view as the default focus for this view and all its ancestors.
- * If the view is inside a keyboard navigation cluster, stops at the root of the cluster since
- * the cluster forms a separate keyboard navigation hierarchy from the default focus point of
- * view.
- */
void setDefaultFocus(View child) {
- if (child.isKeyboardNavigationCluster()) {
+ // Stop at any higher view which is explicitly focused-by-default
+ if (mDefaultFocus != null && mDefaultFocus.isFocusedByDefault()) {
return;
}
}
/**
- * Destroys the default focus chain.
+ * Clears the default-focus chain from {@param child} up to the first parent which has another
+ * default-focusable branch below it or until there is no default-focus chain.
+ *
+ * @param child
*/
- void cleanDefaultFocus(View child) {
- if (mDefaultFocus != child) {
- return;
- }
-
- if (child.isKeyboardNavigationCluster()) {
+ void clearDefaultFocus(View child) {
+ // Stop at any higher view which is explicitly focused-by-default
+ if (mDefaultFocus != child && mDefaultFocus != null
+ && mDefaultFocus.isFocusedByDefault()) {
return;
}
mDefaultFocus = null;
+ // Search child siblings for default focusables.
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View sibling = mChildren[i];
+ if (sibling.isFocusedByDefault()) {
+ mDefaultFocus = sibling;
+ return;
+ } else if (mDefaultFocus == null && sibling.hasDefaultFocus()) {
+ mDefaultFocus = sibling;
+ }
+ }
+
if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).cleanDefaultFocus(this);
+ ((ViewGroup) mParent).clearDefaultFocus(this);
}
}
return mDefaultFocus != null || super.hasDefaultFocus();
}
+ void setFocusInCluster(View child) {
+ // Stop at the root of the cluster
+ if (child.isKeyboardNavigationCluster()) {
+ return;
+ }
+
+ mFocusedInCluster = child;
+
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).setFocusInCluster(this);
+ }
+ }
+
+ void clearFocusInCluster(View child) {
+ if (mFocusedInCluster != child) {
+ return;
+ }
+
+ if (child.isKeyboardNavigationCluster()) {
+ return;
+ }
+
+ mFocusedInCluster = null;
+
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).clearFocusInCluster(this);
+ }
+ }
+
@Override
public void focusableViewAvailable(View v) {
if (mParent != null
}
@Override
- public boolean restoreDefaultFocus(@FocusDirection int direction) {
- if (mDefaultFocus != null && !mDefaultFocus.isKeyboardNavigationCluster()
+ public boolean restoreDefaultFocus() {
+ if (mDefaultFocus != null
&& getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
&& (mDefaultFocus.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && mDefaultFocus.restoreDefaultFocus(direction)) {
+ && mDefaultFocus.restoreDefaultFocus()) {
return true;
}
- return super.restoreDefaultFocus(direction);
+ return super.restoreDefaultFocus();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+ if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
+ && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
+ && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && mFocusedInCluster.restoreFocusInCluster(direction)) {
+ return true;
+ }
+ return super.restoreFocusInCluster(direction);
}
/**
view.unFocus(null);
clearChildFocus = true;
}
- if (view == mDefaultFocus) {
- mDefaultFocus = null;
+ if (view == mFocusedInCluster) {
+ clearFocusInCluster(view);
}
view.clearAccessibilityFocus();
removeFromArray(index);
+ if (view == mDefaultFocus) {
+ clearDefaultFocus(view);
+ }
if (clearChildFocus) {
clearChildFocus(view);
if (!rootViewRequestFocus()) {
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
boolean clearChildFocus = false;
+ View clearDefaultFocus = null;
final View[] children = mChildren;
clearChildFocus = true;
}
if (view == mDefaultFocus) {
- mDefaultFocus = null;
+ clearDefaultFocus = view;
+ }
+ if (view == mFocusedInCluster) {
+ clearFocusInCluster(view);
}
view.clearAccessibilityFocus();
removeFromArray(start, count);
+ if (clearDefaultFocus != null) {
+ clearDefaultFocus(clearDefaultFocus);
+ }
if (clearChildFocus) {
clearChildFocus(focused);
if (!rootViewRequestFocus()) {
boolean clearChildFocus = false;
needGlobalAttributesUpdate(false);
- mDefaultFocus = null;
for (int i = count - 1; i >= 0; i--) {
final View view = children[i];
children[i] = null;
}
+ if (mDefaultFocus != null) {
+ clearDefaultFocus(mDefaultFocus);
+ }
if (clearChildFocus) {
clearChildFocus(focused);
if (!rootViewRequestFocus()) {
child.clearFocus();
}
if (child == mDefaultFocus) {
- mDefaultFocus = null;
+ clearDefaultFocus(child);
+ }
+ if (child == mFocusedInCluster) {
+ clearFocusInCluster(child);
}
child.clearAccessibilityFocus();
Log.d(VIEW_LOG_TAG, output);
mDefaultFocus.debug(depth + 1);
}
+ if (mFocusedInCluster != null) {
+ output = debugIndent(depth);
+ output += "mFocusedInCluster";
+ Log.d(VIEW_LOG_TAG, output);
+ mFocusedInCluster.debug(depth + 1);
+ }
if (mChildrenCount != 0) {
output = debugIndent(depth);
output += "{";