Merge "Allow cluster navigation to jump into touchscreenBlocksFocus"

This commit is contained in:
Evan Rosky
2017-03-29 17:02:13 +00:00
committed by Android (Google) Code Review
6 changed files with 85 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -88,8 +89,9 @@ public class FocusFinder {
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
if (focused != null) {
next = findNextUserSpecifiedFocus(root, focused, direction);
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
return next;
@@ -97,9 +99,9 @@ public class FocusFinder {
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
root.addFocusables(focusables, direction);
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
next = findNextFocus(root, focused, focusedRect, direction, focusables);
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
@@ -107,6 +109,35 @@ public class FocusFinder {
return next;
}
/**
* Returns the "effective" root of a view. The "effective" root is the closest ancestor
* within-which focus should cycle.
* <p>
* For example: normal focus navigation would stay within a ViewGroup marked as
* touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out.
* @return the "effective" root of {@param focused}
*/
private ViewGroup getEffectiveRoot(ViewGroup root, View focused) {
if (focused == null) {
return root;
}
ViewParent effective = focused.getParent();
do {
if (effective == root) {
return root;
}
ViewGroup vg = (ViewGroup) effective;
if (vg.getTouchscreenBlocksFocus()
&& focused.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN)
&& vg.isKeyboardNavigationCluster()) {
return vg;
}
effective = effective.getParent();
} while (effective != null);
return root;
}
/**
* Find the root of the next keyboard navigation cluster after the current one.
* @param root The view tree to look inside. Cannot be null

View File

@@ -1190,7 +1190,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen());
final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
if (focusSelf) {
@@ -1199,7 +1200,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return;
}
if (shouldBlockFocusForTouchscreen()) {
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
@@ -1234,7 +1235,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
final int focusableCount = views.size();
super.addKeyboardNavigationClusters(views, direction);
if (isKeyboardNavigationCluster()) {
// Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily
// disable touchscreenBlocksFocus to evaluate whether it contains focusables.
final boolean blockedFocus = getTouchscreenBlocksFocus();
try {
setTouchscreenBlocksFocusNoRefocus(false);
super.addKeyboardNavigationClusters(views, direction);
} finally {
setTouchscreenBlocksFocusNoRefocus(blockedFocus);
}
} else {
super.addKeyboardNavigationClusters(views, direction);
}
if (focusableCount != views.size()) {
// No need to look for groups inside a group.
@@ -1280,6 +1293,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) {
if (touchscreenBlocksFocus) {
mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
} else {
mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
}
}
/**
* Check whether this ViewGroup should ignore focus requests for itself and its children.
*/
@@ -1288,8 +1309,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
boolean shouldBlockFocusForTouchscreen() {
// There is a special case for keyboard-navigation clusters. We allow cluster navigation
// to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
// cluster, focus is free to move around within it.
return getTouchscreenBlocksFocus() &&
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
&& (!hasFocus() || !isKeyboardNavigationCluster());
}
@Override
@@ -3175,6 +3200,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@TestApi
@Override
public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
// Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups.
if (isKeyboardNavigationCluster()) {
final boolean blockedFocus = getTouchscreenBlocksFocus();
try {
setTouchscreenBlocksFocusNoRefocus(false);
return restoreFocusInClusterInternal(direction);
} finally {
setTouchscreenBlocksFocusNoRefocus(blockedFocus);
}
} else {
return restoreFocusInClusterInternal(direction);
}
}
private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
&& getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
&& (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE

View File

@@ -343,6 +343,9 @@ public class Toolbar extends ViewGroup {
final ViewGroup vgParent = (ViewGroup) parent;
if (vgParent.isKeyboardNavigationCluster()) {
setKeyboardNavigationCluster(false);
if (vgParent.getTouchscreenBlocksFocus()) {
setTouchscreenBlocksFocus(false);
}
break;
}
parent = vgParent.getParent();

View File

@@ -35,6 +35,7 @@ This is an optimized layout for a screen with the Action Bar enabled.
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
@@ -54,6 +55,7 @@ This is an optimized layout for a screen with the Action Bar enabled.
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>

View File

@@ -35,6 +35,7 @@ This is an optimized layout for a screen with a toolbar enabled.
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="top">
<Toolbar

View File

@@ -1249,6 +1249,7 @@ please see styles_device_defaults.xml.
<item name="collapseContentDescription">@string/toolbar_collapse_description</item>
<item name="contentInsetStart">16dp</item>
<item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
<item name="touchscreenBlocksFocus">true</item>
<item name="keyboardNavigationCluster">true</item>
</style>