Loading core/java/android/view/FocusFinder.java +40 −6 Original line number Diff line number Diff line Loading @@ -54,7 +54,9 @@ public class FocusFinder { final Rect mOtherRect = new Rect(); final Rect mBestCandidateRect = new Rect(); private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator = new UserSpecifiedFocusComparator(); new UserSpecifiedFocusComparator((v) -> v.getNextFocusForwardId()); private final UserSpecifiedFocusComparator mUserSpecifiedClusterComparator = new UserSpecifiedFocusComparator((v) -> v.getNextClusterForwardId()); private final FocusComparator mFocusComparator = new FocusComparator(); private final ArrayList<View> mTempList = new ArrayList<View>(); Loading Loading @@ -150,6 +152,12 @@ public class FocusFinder { @Nullable View currentCluster, @View.FocusDirection int direction) { View next = null; if (currentCluster != null) { next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction); if (next != null) { return next; } } final ArrayList<View> clusters = mTempList; try { Loading @@ -165,6 +173,16 @@ public class FocusFinder { return next; } private View findNextUserSpecifiedKeyboardNavigationCluster(View root, View currentCluster, int direction) { View userSetNextCluster = currentCluster.findUserSetNextKeyboardNavigationCluster(root, direction); if (userSetNextCluster != null && userSetNextCluster.hasFocusable()) { return userSetNextCluster; } return null; } private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); Loading Loading @@ -238,6 +256,13 @@ public class FocusFinder { View currentCluster, List<View> clusters, @View.FocusDirection int direction) { try { // Note: This sort is stable. mUserSpecifiedClusterComparator.setFocusables(clusters); Collections.sort(clusters, mUserSpecifiedClusterComparator); } finally { mUserSpecifiedClusterComparator.recycle(); } final int count = clusters.size(); switch (direction) { Loading Loading @@ -802,6 +827,15 @@ public class FocusFinder { private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>(); private final NextIdGetter mNextIdGetter; public interface NextIdGetter { int get(View view); } UserSpecifiedFocusComparator(NextIdGetter nextIdGetter) { mNextIdGetter = nextIdGetter; } public void recycle() { mFocusables.clear(); Loading @@ -810,14 +844,14 @@ public class FocusFinder { mOriginalOrdinal.clear(); } public void setFocusables(ArrayList<View> focusables) { public void setFocusables(List<View> focusables) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final int id = view.getId(); if (isValidId(id)) { mFocusables.put(id, view); } final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId)) { mIsConnectedTo.put(nextId, true); } Loading @@ -825,7 +859,7 @@ public class FocusFinder { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId) && !mIsConnectedTo.get(view.getId())) { setHeadOfChain(view); } Loading @@ -838,7 +872,7 @@ public class FocusFinder { private void setHeadOfChain(View head) { for (View view = head; view != null; view = mFocusables.get(view.getNextFocusForwardId())) { view = mFocusables.get(mNextIdGetter.get(view))) { final View otherHead = mHeadsOfChains.get(view); if (otherHead != null) { if (otherHead == head) { Loading Loading @@ -866,7 +900,7 @@ public class FocusFinder { return -1; // first is the head, it should be first } else if (second == firstHead) { return 1; // second is the head, it should be first } else if (isValidId(first.getNextFocusForwardId())) { } else if (isValidId(mNextIdGetter.get(first))) { return -1; // first is not the end of the chain } else { return 1; // first is end of chain Loading core/java/android/view/View.java +29 −1 Original line number Diff line number Diff line Loading @@ -707,13 +707,16 @@ import java.util.function.Predicate; * @attr ref android.R.styleable#View_isScrollContainer * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusableInTouchMode * @attr ref android.R.styleable#View_focusedByDefault * @attr ref android.R.styleable#View_hapticFeedbackEnabled * @attr ref android.R.styleable#View_keepScreenOn * @attr ref android.R.styleable#View_keyboardNavigationCluster * @attr ref android.R.styleable#View_layerType * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minWidth * @attr ref android.R.styleable#View_nextClusterForward * @attr ref android.R.styleable#View_nextFocusDown * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusRight Loading Loading @@ -4084,7 +4087,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mNextFocusForwardId = View.NO_ID; /** * User-specified next keyboard navigation cluster. * User-specified next keyboard navigation cluster in the {@link #FOCUS_FORWARD} direction. * * @see #findUserSetNextKeyboardNavigationCluster(View, int) */ int mNextClusterForwardId = View.NO_ID; Loading Loading @@ -10017,6 +10022,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } /** * If a user manually specified the next keyboard-navigation cluster for a particular direction, * use the root to look up the view. * * @param root the root view of the hierarchy containing this view * @param direction {@link #FOCUS_FORWARD} or {@link #FOCUS_BACKWARD} * @return the user-specified next cluster, or {@code null} if there is none */ View findUserSetNextKeyboardNavigationCluster(View root, @FocusDirection int direction) { switch (direction) { case FOCUS_FORWARD: if (mNextClusterForwardId == View.NO_ID) return null; return findViewInsideOutShouldExist(root, mNextClusterForwardId); case FOCUS_BACKWARD: { if (mID == View.NO_ID) return null; final int id = mID; return root.findViewByPredicateInsideOut(this, (Predicate<View>) t -> t.mNextClusterForwardId == id); } } return null; } private View findViewInsideOutShouldExist(View root, int id) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); Loading core/java/android/view/ViewGroup.java +9 −6 Original line number Diff line number Diff line Loading @@ -1258,15 +1258,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; } final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; int count = 0; final View[] visibleChildren = new View[mChildrenCount]; for (int i = 0; i < mChildrenCount; ++i) { final View child = mChildren[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.addKeyboardNavigationClusters(views, direction); visibleChildren[count++] = child; } } Arrays.sort(visibleChildren, 0, count, FocusFinder.getFocusComparator(this, false)); for (int i = 0; i < count; ++i) { visibleChildren[i].addKeyboardNavigationClusters(views, direction); } } /** Loading core/java/android/view/ViewRootImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -4660,6 +4660,7 @@ public final class ViewRootImpl implements ViewParent, if (cluster != null && cluster.isRootNamespace()) { // the default cluster. Try to find a non-clustered view to focus. if (cluster.restoreFocusNotInCluster()) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } // otherwise skip to next actual cluster Loading @@ -4667,6 +4668,7 @@ public final class ViewRootImpl implements ViewParent, } if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } Loading Loading
core/java/android/view/FocusFinder.java +40 −6 Original line number Diff line number Diff line Loading @@ -54,7 +54,9 @@ public class FocusFinder { final Rect mOtherRect = new Rect(); final Rect mBestCandidateRect = new Rect(); private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator = new UserSpecifiedFocusComparator(); new UserSpecifiedFocusComparator((v) -> v.getNextFocusForwardId()); private final UserSpecifiedFocusComparator mUserSpecifiedClusterComparator = new UserSpecifiedFocusComparator((v) -> v.getNextClusterForwardId()); private final FocusComparator mFocusComparator = new FocusComparator(); private final ArrayList<View> mTempList = new ArrayList<View>(); Loading Loading @@ -150,6 +152,12 @@ public class FocusFinder { @Nullable View currentCluster, @View.FocusDirection int direction) { View next = null; if (currentCluster != null) { next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction); if (next != null) { return next; } } final ArrayList<View> clusters = mTempList; try { Loading @@ -165,6 +173,16 @@ public class FocusFinder { return next; } private View findNextUserSpecifiedKeyboardNavigationCluster(View root, View currentCluster, int direction) { View userSetNextCluster = currentCluster.findUserSetNextKeyboardNavigationCluster(root, direction); if (userSetNextCluster != null && userSetNextCluster.hasFocusable()) { return userSetNextCluster; } return null; } private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); Loading Loading @@ -238,6 +256,13 @@ public class FocusFinder { View currentCluster, List<View> clusters, @View.FocusDirection int direction) { try { // Note: This sort is stable. mUserSpecifiedClusterComparator.setFocusables(clusters); Collections.sort(clusters, mUserSpecifiedClusterComparator); } finally { mUserSpecifiedClusterComparator.recycle(); } final int count = clusters.size(); switch (direction) { Loading Loading @@ -802,6 +827,15 @@ public class FocusFinder { private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>(); private final NextIdGetter mNextIdGetter; public interface NextIdGetter { int get(View view); } UserSpecifiedFocusComparator(NextIdGetter nextIdGetter) { mNextIdGetter = nextIdGetter; } public void recycle() { mFocusables.clear(); Loading @@ -810,14 +844,14 @@ public class FocusFinder { mOriginalOrdinal.clear(); } public void setFocusables(ArrayList<View> focusables) { public void setFocusables(List<View> focusables) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final int id = view.getId(); if (isValidId(id)) { mFocusables.put(id, view); } final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId)) { mIsConnectedTo.put(nextId, true); } Loading @@ -825,7 +859,7 @@ public class FocusFinder { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId) && !mIsConnectedTo.get(view.getId())) { setHeadOfChain(view); } Loading @@ -838,7 +872,7 @@ public class FocusFinder { private void setHeadOfChain(View head) { for (View view = head; view != null; view = mFocusables.get(view.getNextFocusForwardId())) { view = mFocusables.get(mNextIdGetter.get(view))) { final View otherHead = mHeadsOfChains.get(view); if (otherHead != null) { if (otherHead == head) { Loading Loading @@ -866,7 +900,7 @@ public class FocusFinder { return -1; // first is the head, it should be first } else if (second == firstHead) { return 1; // second is the head, it should be first } else if (isValidId(first.getNextFocusForwardId())) { } else if (isValidId(mNextIdGetter.get(first))) { return -1; // first is not the end of the chain } else { return 1; // first is end of chain Loading
core/java/android/view/View.java +29 −1 Original line number Diff line number Diff line Loading @@ -707,13 +707,16 @@ import java.util.function.Predicate; * @attr ref android.R.styleable#View_isScrollContainer * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusableInTouchMode * @attr ref android.R.styleable#View_focusedByDefault * @attr ref android.R.styleable#View_hapticFeedbackEnabled * @attr ref android.R.styleable#View_keepScreenOn * @attr ref android.R.styleable#View_keyboardNavigationCluster * @attr ref android.R.styleable#View_layerType * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minWidth * @attr ref android.R.styleable#View_nextClusterForward * @attr ref android.R.styleable#View_nextFocusDown * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusRight Loading Loading @@ -4084,7 +4087,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mNextFocusForwardId = View.NO_ID; /** * User-specified next keyboard navigation cluster. * User-specified next keyboard navigation cluster in the {@link #FOCUS_FORWARD} direction. * * @see #findUserSetNextKeyboardNavigationCluster(View, int) */ int mNextClusterForwardId = View.NO_ID; Loading Loading @@ -10017,6 +10022,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } /** * If a user manually specified the next keyboard-navigation cluster for a particular direction, * use the root to look up the view. * * @param root the root view of the hierarchy containing this view * @param direction {@link #FOCUS_FORWARD} or {@link #FOCUS_BACKWARD} * @return the user-specified next cluster, or {@code null} if there is none */ View findUserSetNextKeyboardNavigationCluster(View root, @FocusDirection int direction) { switch (direction) { case FOCUS_FORWARD: if (mNextClusterForwardId == View.NO_ID) return null; return findViewInsideOutShouldExist(root, mNextClusterForwardId); case FOCUS_BACKWARD: { if (mID == View.NO_ID) return null; final int id = mID; return root.findViewByPredicateInsideOut(this, (Predicate<View>) t -> t.mNextClusterForwardId == id); } } return null; } private View findViewInsideOutShouldExist(View root, int id) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); Loading
core/java/android/view/ViewGroup.java +9 −6 Original line number Diff line number Diff line Loading @@ -1258,15 +1258,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; } final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; int count = 0; final View[] visibleChildren = new View[mChildrenCount]; for (int i = 0; i < mChildrenCount; ++i) { final View child = mChildren[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.addKeyboardNavigationClusters(views, direction); visibleChildren[count++] = child; } } Arrays.sort(visibleChildren, 0, count, FocusFinder.getFocusComparator(this, false)); for (int i = 0; i < count; ++i) { visibleChildren[i].addKeyboardNavigationClusters(views, direction); } } /** Loading
core/java/android/view/ViewRootImpl.java +2 −0 Original line number Diff line number Diff line Loading @@ -4660,6 +4660,7 @@ public final class ViewRootImpl implements ViewParent, if (cluster != null && cluster.isRootNamespace()) { // the default cluster. Try to find a non-clustered view to focus. if (cluster.restoreFocusNotInCluster()) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } // otherwise skip to next actual cluster Loading @@ -4667,6 +4668,7 @@ public final class ViewRootImpl implements ViewParent, } if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } Loading