Loading core/java/android/view/FocusFinder.java +40 −6 Original line number Original line Diff line number Diff line Loading @@ -54,7 +54,9 @@ public class FocusFinder { final Rect mOtherRect = new Rect(); final Rect mOtherRect = new Rect(); final Rect mBestCandidateRect = new Rect(); final Rect mBestCandidateRect = new Rect(); private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator = 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 FocusComparator mFocusComparator = new FocusComparator(); private final ArrayList<View> mTempList = new ArrayList<View>(); private final ArrayList<View> mTempList = new ArrayList<View>(); Loading Loading @@ -150,6 +152,12 @@ public class FocusFinder { @Nullable View currentCluster, @Nullable View currentCluster, @View.FocusDirection int direction) { @View.FocusDirection int direction) { View next = null; View next = null; if (currentCluster != null) { next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction); if (next != null) { return next; } } final ArrayList<View> clusters = mTempList; final ArrayList<View> clusters = mTempList; try { try { Loading @@ -165,6 +173,16 @@ public class FocusFinder { return next; 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) { private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); View userSetNextFocus = focused.findUserSetNextFocus(root, direction); Loading Loading @@ -238,6 +256,13 @@ public class FocusFinder { View currentCluster, View currentCluster, List<View> clusters, List<View> clusters, @View.FocusDirection int direction) { @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(); final int count = clusters.size(); switch (direction) { switch (direction) { Loading Loading @@ -802,6 +827,15 @@ public class FocusFinder { private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>(); 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() { public void recycle() { mFocusables.clear(); mFocusables.clear(); Loading @@ -810,14 +844,14 @@ public class FocusFinder { mOriginalOrdinal.clear(); mOriginalOrdinal.clear(); } } public void setFocusables(ArrayList<View> focusables) { public void setFocusables(List<View> focusables) { for (int i = focusables.size() - 1; i >= 0; i--) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final View view = focusables.get(i); final int id = view.getId(); final int id = view.getId(); if (isValidId(id)) { if (isValidId(id)) { mFocusables.put(id, view); mFocusables.put(id, view); } } final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId)) { if (isValidId(nextId)) { mIsConnectedTo.put(nextId, true); mIsConnectedTo.put(nextId, true); } } Loading @@ -825,7 +859,7 @@ public class FocusFinder { for (int i = focusables.size() - 1; i >= 0; i--) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(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())) { if (isValidId(nextId) && !mIsConnectedTo.get(view.getId())) { setHeadOfChain(view); setHeadOfChain(view); } } Loading @@ -838,7 +872,7 @@ public class FocusFinder { private void setHeadOfChain(View head) { private void setHeadOfChain(View head) { for (View view = head; view != null; for (View view = head; view != null; view = mFocusables.get(view.getNextFocusForwardId())) { view = mFocusables.get(mNextIdGetter.get(view))) { final View otherHead = mHeadsOfChains.get(view); final View otherHead = mHeadsOfChains.get(view); if (otherHead != null) { if (otherHead != null) { if (otherHead == head) { if (otherHead == head) { Loading Loading @@ -866,7 +900,7 @@ public class FocusFinder { return -1; // first is the head, it should be first return -1; // first is the head, it should be first } else if (second == firstHead) { } else if (second == firstHead) { return 1; // second is the head, it should be first 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 return -1; // first is not the end of the chain } else { } else { return 1; // first is end of chain return 1; // first is end of chain Loading core/java/android/view/View.java +29 −1 Original line number Original line 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_isScrollContainer * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusableInTouchMode * @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_hapticFeedbackEnabled * @attr ref android.R.styleable#View_keepScreenOn * @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_layerType * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minWidth * @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_nextFocusDown * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusRight * @attr ref android.R.styleable#View_nextFocusRight Loading Loading @@ -4076,7 +4079,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mNextFocusForwardId = View.NO_ID; 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; int mNextClusterForwardId = View.NO_ID; Loading Loading @@ -9968,6 +9973,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; 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) { private View findViewInsideOutShouldExist(View root, int id) { if (mMatchIdPredicate == null) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); mMatchIdPredicate = new MatchIdPredicate(); Loading core/java/android/view/ViewGroup.java +9 −6 Original line number Original line Diff line number Diff line Loading @@ -1258,15 +1258,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; return; } } final int count = mChildrenCount; int count = 0; final View[] children = mChildren; final View[] visibleChildren = new View[mChildrenCount]; for (int i = 0; i < mChildrenCount; ++i) { for (int i = 0; i < count; i++) { final View child = mChildren[i]; final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { 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 Original line Diff line number Diff line Loading @@ -4660,6 +4660,7 @@ public final class ViewRootImpl implements ViewParent, if (cluster != null && cluster.isRootNamespace()) { if (cluster != null && cluster.isRootNamespace()) { // the default cluster. Try to find a non-clustered view to focus. // the default cluster. Try to find a non-clustered view to focus. if (cluster.restoreFocusNotInCluster()) { if (cluster.restoreFocusNotInCluster()) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; return true; } } // otherwise skip to next actual cluster // otherwise skip to next actual cluster Loading @@ -4667,6 +4668,7 @@ public final class ViewRootImpl implements ViewParent, } } if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; return true; } } Loading Loading
core/java/android/view/FocusFinder.java +40 −6 Original line number Original line Diff line number Diff line Loading @@ -54,7 +54,9 @@ public class FocusFinder { final Rect mOtherRect = new Rect(); final Rect mOtherRect = new Rect(); final Rect mBestCandidateRect = new Rect(); final Rect mBestCandidateRect = new Rect(); private final UserSpecifiedFocusComparator mUserSpecifiedFocusComparator = 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 FocusComparator mFocusComparator = new FocusComparator(); private final ArrayList<View> mTempList = new ArrayList<View>(); private final ArrayList<View> mTempList = new ArrayList<View>(); Loading Loading @@ -150,6 +152,12 @@ public class FocusFinder { @Nullable View currentCluster, @Nullable View currentCluster, @View.FocusDirection int direction) { @View.FocusDirection int direction) { View next = null; View next = null; if (currentCluster != null) { next = findNextUserSpecifiedKeyboardNavigationCluster(root, currentCluster, direction); if (next != null) { return next; } } final ArrayList<View> clusters = mTempList; final ArrayList<View> clusters = mTempList; try { try { Loading @@ -165,6 +173,16 @@ public class FocusFinder { return next; 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) { private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); View userSetNextFocus = focused.findUserSetNextFocus(root, direction); Loading Loading @@ -238,6 +256,13 @@ public class FocusFinder { View currentCluster, View currentCluster, List<View> clusters, List<View> clusters, @View.FocusDirection int direction) { @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(); final int count = clusters.size(); switch (direction) { switch (direction) { Loading Loading @@ -802,6 +827,15 @@ public class FocusFinder { private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final SparseBooleanArray mIsConnectedTo = new SparseBooleanArray(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, View> mHeadsOfChains = new ArrayMap<View, View>(); private final ArrayMap<View, Integer> mOriginalOrdinal = new ArrayMap<>(); 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() { public void recycle() { mFocusables.clear(); mFocusables.clear(); Loading @@ -810,14 +844,14 @@ public class FocusFinder { mOriginalOrdinal.clear(); mOriginalOrdinal.clear(); } } public void setFocusables(ArrayList<View> focusables) { public void setFocusables(List<View> focusables) { for (int i = focusables.size() - 1; i >= 0; i--) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(i); final View view = focusables.get(i); final int id = view.getId(); final int id = view.getId(); if (isValidId(id)) { if (isValidId(id)) { mFocusables.put(id, view); mFocusables.put(id, view); } } final int nextId = view.getNextFocusForwardId(); final int nextId = mNextIdGetter.get(view); if (isValidId(nextId)) { if (isValidId(nextId)) { mIsConnectedTo.put(nextId, true); mIsConnectedTo.put(nextId, true); } } Loading @@ -825,7 +859,7 @@ public class FocusFinder { for (int i = focusables.size() - 1; i >= 0; i--) { for (int i = focusables.size() - 1; i >= 0; i--) { final View view = focusables.get(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())) { if (isValidId(nextId) && !mIsConnectedTo.get(view.getId())) { setHeadOfChain(view); setHeadOfChain(view); } } Loading @@ -838,7 +872,7 @@ public class FocusFinder { private void setHeadOfChain(View head) { private void setHeadOfChain(View head) { for (View view = head; view != null; for (View view = head; view != null; view = mFocusables.get(view.getNextFocusForwardId())) { view = mFocusables.get(mNextIdGetter.get(view))) { final View otherHead = mHeadsOfChains.get(view); final View otherHead = mHeadsOfChains.get(view); if (otherHead != null) { if (otherHead != null) { if (otherHead == head) { if (otherHead == head) { Loading Loading @@ -866,7 +900,7 @@ public class FocusFinder { return -1; // first is the head, it should be first return -1; // first is the head, it should be first } else if (second == firstHead) { } else if (second == firstHead) { return 1; // second is the head, it should be first 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 return -1; // first is not the end of the chain } else { } else { return 1; // first is end of chain return 1; // first is end of chain Loading
core/java/android/view/View.java +29 −1 Original line number Original line 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_isScrollContainer * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusable * @attr ref android.R.styleable#View_focusableInTouchMode * @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_hapticFeedbackEnabled * @attr ref android.R.styleable#View_keepScreenOn * @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_layerType * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_layoutDirection * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_longClickable * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minHeight * @attr ref android.R.styleable#View_minWidth * @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_nextFocusDown * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusRight * @attr ref android.R.styleable#View_nextFocusRight Loading Loading @@ -4076,7 +4079,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mNextFocusForwardId = View.NO_ID; 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; int mNextClusterForwardId = View.NO_ID; Loading Loading @@ -9968,6 +9973,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; 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) { private View findViewInsideOutShouldExist(View root, int id) { if (mMatchIdPredicate == null) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); mMatchIdPredicate = new MatchIdPredicate(); Loading
core/java/android/view/ViewGroup.java +9 −6 Original line number Original line Diff line number Diff line Loading @@ -1258,15 +1258,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return; return; } } final int count = mChildrenCount; int count = 0; final View[] children = mChildren; final View[] visibleChildren = new View[mChildrenCount]; for (int i = 0; i < mChildrenCount; ++i) { for (int i = 0; i < count; i++) { final View child = mChildren[i]; final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { 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 Original line Diff line number Diff line Loading @@ -4660,6 +4660,7 @@ public final class ViewRootImpl implements ViewParent, if (cluster != null && cluster.isRootNamespace()) { if (cluster != null && cluster.isRootNamespace()) { // the default cluster. Try to find a non-clustered view to focus. // the default cluster. Try to find a non-clustered view to focus. if (cluster.restoreFocusNotInCluster()) { if (cluster.restoreFocusNotInCluster()) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; return true; } } // otherwise skip to next actual cluster // otherwise skip to next actual cluster Loading @@ -4667,6 +4668,7 @@ public final class ViewRootImpl implements ViewParent, } } if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; return true; } } Loading