1049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette/* 2049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Copyright (C) 2015 The Android Open Source Project 3049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 4049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License"); 5049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * you may not use this file except in compliance with the License. 6049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * You may obtain a copy of the License at 7049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 8049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * http://www.apache.org/licenses/LICENSE-2.0 9049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 10049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Unless required by applicable law or agreed to in writing, software 11049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS, 12049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * See the License for the specific language governing permissions and 14049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * limitations under the License. 15049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 16049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 17049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverettepackage android.support.v4.widget; 18049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 19049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.graphics.Rect; 20049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.NonNull; 21049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.annotation.Nullable; 22049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.view.View; 23049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 24049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.v4.view.ViewCompat.FocusRealDirection; 25049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport android.support.v4.view.ViewCompat.FocusRelativeDirection; 26049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 27049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.ArrayList; 28049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.Collections; 29049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteimport java.util.Comparator; 30049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 31049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette/** 32049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Implements absolute and relative focus movement strategies. Adapted from 33049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * android.view.FocusFinder to work with generic collections of bounded items. 34049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 35049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viveretteclass FocusStrategy { 364d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas public static <L, T> T findNextFocusInRelativeDirection(@NonNull L focusables, 374d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas @NonNull CollectionAdapter<L, T> collectionAdapter, @NonNull BoundsAdapter<T> adapter, 38049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @Nullable T focused, @FocusRelativeDirection int direction, boolean isLayoutRtl, 39049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette boolean wrap) { 40049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int count = collectionAdapter.size(focusables); 41049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final ArrayList<T> sortedFocusables = new ArrayList<>(count); 42049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette for (int i = 0; i < count; i++) { 43049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette sortedFocusables.add(collectionAdapter.get(focusables, i)); 44049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 45049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 46049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final SequentialComparator<T> comparator = new SequentialComparator<>(isLayoutRtl, adapter); 47049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette Collections.sort(sortedFocusables, comparator); 48049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 49049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 50049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_FORWARD: 51049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return getNextFocusable(focused, sortedFocusables, wrap); 52049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_BACKWARD: 53049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return getPreviousFocusable(focused, sortedFocusables, wrap); 54049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette default: 55049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 56049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_FORWARD, FOCUS_BACKWARD}."); 57049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 58049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 59049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 60049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static <T> T getNextFocusable(T focused, ArrayList<T> focusables, boolean wrap) { 61049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int count = focusables.size(); 62049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 63049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // The position of the next focusable item, which is the first item if 64049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // no item is currently focused. 65049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int position = (focused == null ? -1 : focusables.lastIndexOf(focused)) + 1; 66049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (position < count) { 67049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return focusables.get(position); 68049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (wrap && count > 0) { 69049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return focusables.get(0); 70049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else { 71049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return null; 72049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 73049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 74049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 75049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static <T> T getPreviousFocusable(T focused, ArrayList<T> focusables, boolean wrap) { 76049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int count = focusables.size(); 77049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 78049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // The position of the previous focusable item, which is the last item 79049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // if no item is currently focused. 80049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int position = (focused == null ? count : focusables.indexOf(focused)) - 1; 81049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (position >= 0) { 82049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return focusables.get(position); 83049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (wrap && count > 0) { 84049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return focusables.get(count - 1); 85049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else { 86049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return null; 87049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 88049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 89049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 90049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 91049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Sorts views according to their visual layout and geometry for default tab order. 92049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * This is used for sequential focus traversal. 93049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 94049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static class SequentialComparator<T> implements Comparator<T> { 95049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private final Rect mTemp1 = new Rect(); 96049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private final Rect mTemp2 = new Rect(); 97049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 98049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private final boolean mIsLayoutRtl; 99049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private final BoundsAdapter<T> mAdapter; 100049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 1014d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) { 102049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mIsLayoutRtl = isLayoutRtl; 103049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter = adapter; 104049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 105049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 10615375aa6fd54b036f97f99229aefab2822c8a1c9Aurimas Liutikas @Override 107049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public int compare(T first, T second) { 108049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect firstRect = mTemp1; 109049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect secondRect = mTemp2; 110049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 111049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter.obtainBounds(first, firstRect); 112049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter.obtainBounds(second, secondRect); 113049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 114049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (firstRect.top < secondRect.top) { 115049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return -1; 116049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.top > secondRect.top) { 117049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 1; 118049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.left < secondRect.left) { 119049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? 1 : -1; 120049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.left > secondRect.left) { 121049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? -1 : 1; 122049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.bottom < secondRect.bottom) { 123049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return -1; 124049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.bottom > secondRect.bottom) { 125049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 1; 126049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.right < secondRect.right) { 127049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? 1 : -1; 128049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.right > secondRect.right) { 129049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? -1 : 1; 130049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else { 131049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // The view are distinct but completely coincident so we 132049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // consider them equal for our purposes. Since the sort is 133049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // stable, this means that the views will retain their 134049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // layout order relative to one another. 135049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 0; 136049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 137049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 138049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 139049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 1404d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas public static <L, T> T findNextFocusInAbsoluteDirection(@NonNull L focusables, 1414d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas @NonNull CollectionAdapter<L, T> collectionAdapter, @NonNull BoundsAdapter<T> adapter, 142049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @Nullable T focused, @NonNull Rect focusedRect, int direction) { 143049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // Initialize the best candidate to something impossible so that 144049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // the first plausible view will become the best choice. 145049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect bestCandidateRect = new Rect(focusedRect); 146049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 147049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 148049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 149049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(focusedRect.width() + 1, 0); 150049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 151049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 152049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(-(focusedRect.width() + 1), 0); 153049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 154049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 155049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(0, focusedRect.height() + 1); 156049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 157049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 158049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(0, -(focusedRect.height() + 1)); 159049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 160049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette default: 161049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 162049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 163049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 164049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 165049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette T closest = null; 166049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 167049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int count = collectionAdapter.size(focusables); 168049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect focusableRect = new Rect(); 169049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette for (int i = 0; i < count; i++) { 170049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final T focusable = collectionAdapter.get(focusables, i); 171049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (focusable == focused) { 172049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette continue; 173049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 174049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 175049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // get focus bounds of other view 176049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette adapter.obtainBounds(focusable, focusableRect); 177049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) { 178049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.set(focusableRect); 179049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette closest = focusable; 180049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 181049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 182049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 183049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return closest; 184049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 185049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 186049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 187049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Is candidate a better candidate than currentBest for a focus search 188049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * in a particular direction from a source rect? This is the core 189049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * routine that determines the order of focus searching. 190049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 191049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 192049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param source the source from which we are searching 193049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param candidate the candidate rectangle 194049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param currentBest the current best rectangle 195049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return {@code true} if the candidate rectangle is a better than the 196049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * current best rectangle, {@code false} otherwise 197049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 198049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isBetterCandidate( 199049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction, @NonNull Rect source, 200049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect candidate, @NonNull Rect currentBest) { 201049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // To be a better candidate, need to at least be a candidate in the 202049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // first place. :) 203049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isCandidate(source, candidate, direction)) { 204049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 205049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 206049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 207049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // We know that candidateRect is a candidate. If currentBest is not 208049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // a candidate, candidateRect is better. 209049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isCandidate(source, currentBest, direction)) { 210049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 211049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 212049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 213049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If candidateRect is better by beam, it wins. 214049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (beamBeats(direction, source, candidate, currentBest)) { 215049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 216049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 217049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 218049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If currentBest is better, then candidateRect cant' be. :) 219049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (beamBeats(direction, source, currentBest, candidate)) { 220049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 221049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 222049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 223049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // Otherwise, do fudge-tastic comparison of the major and minor 224049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // axis. 225049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int candidateDist = getWeightedDistanceFor( 226049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette majorAxisDistance(direction, source, candidate), 227049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette minorAxisDistance(direction, source, candidate)); 228049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int currentBestDist = getWeightedDistanceFor( 229049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette majorAxisDistance(direction, source, currentBest), 230049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette minorAxisDistance(direction, source, currentBest)); 231049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return candidateDist < currentBestDist; 232049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 233049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 234049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 235049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * One rectangle may be another candidate than another by virtue of 236049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * being exclusively in the beam of the source rect. 237049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 238049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return whether rect1 is a better candidate than rect2 by virtue of 239049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * it being in source's beam 240049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 241049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean beamBeats(@FocusRealDirection int direction, 242049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) { 243049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); 244049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2); 245049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 246049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If rect1 isn't exclusively in the src beam, it doesn't win. 247049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (rect2InSrcBeam || !rect1InSrcBeam) { 248049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 249049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 250049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 251049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // We know rect1 is in the beam, and rect2 is not. 252049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 253049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If rect1 is to the direction of, and rect2 is not, rect1 wins. 254049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For example, for direction left, if rect1 is to the left of the 255049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // source and rect2 is below, then we always prefer the in beam 256049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // rect1, since rect2 could be reached by going down. 257049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isToDirectionOf(direction, source, rect2)) { 258049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 259049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 260049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 261049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For horizontal directions, being exclusively in beam always 262049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // wins. 263049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) { 264049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 265049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 266049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 267049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For vertical directions, beams only beat up to a point: now, as 268049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // long as rect2 isn't completely closer, rect1 wins, e.g. for 269049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // direction down, completely closer means for rect2's top edge to 270049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // be closer to the source's top edge than rect1's bottom edge. 271049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return majorAxisDistance(direction, source, rect1) 272049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette < majorAxisDistanceToFarEdge(direction, source, rect2); 273049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 274049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 275049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 276049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Fudge-factor opportunity: how to calculate distance given major and 277049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * minor axis distances. 278049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * <p/> 279049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Warning: this fudge factor is finely tuned, be sure to run all focus 280049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * tests if you dare tweak it. 281049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 282049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) { 283049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 13 * majorAxisDistance * majorAxisDistance 284049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + minorAxisDistance * minorAxisDistance; 285049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 286049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 287049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 288049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Is destRect a candidate for the next focus given the direction? This 289049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * checks whether the dest is at least partially to the direction of 290049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * (e.g. left of) from source. 291049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * <p/> 292049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Includes an edge case for an empty rect,which is used in some cases 293049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * when searching from a point on the screen. 294049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 295049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect, 296049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction) { 297049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 298049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 299049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.right > destRect.right || srcRect.left >= destRect.right) 300049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.left > destRect.left; 301049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 302049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.left < destRect.left || srcRect.right <= destRect.left) 303049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.right < destRect.right; 304049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 305049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) 306049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.top > destRect.top; 307049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 308049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) 309049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.bottom < destRect.bottom; 310049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 311049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 312049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 313049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 314049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 315049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 316049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 317049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap? 318049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 319049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 320049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param rect1 the first rectangle 321049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param rect2 the second rectangle 322049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return whether the beams overlap 323049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 324049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean beamsOverlap(@FocusRealDirection int direction, 325049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect rect1, @NonNull Rect rect2) { 326049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 327049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 328049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 329049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom); 330049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 331049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 332049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (rect2.right >= rect1.left) && (rect2.left <= rect1.right); 333049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 334049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 335049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 336049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 337049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 338049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 339049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * e.g for left, is 'to left of' 340049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 341049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isToDirectionOf(@FocusRealDirection int direction, 342049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect src, @NonNull Rect dest) { 343049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 344049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 345049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.left >= dest.right; 346049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 347049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.right <= dest.left; 348049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 349049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.top >= dest.bottom; 350049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 351049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.bottom <= dest.top; 352049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 353049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 354049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 355049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 356049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 357049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 358049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance from the edge furthest in the given direction 359049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * of source to the edge nearest in the given direction of 360049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * dest. If the dest is not in the direction from source, 361049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * returns 0. 362049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 363049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistance(@FocusRealDirection int direction, 364049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 365049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.max(0, majorAxisDistanceRaw(direction, source, dest)); 366049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 367049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 368049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceRaw(@FocusRealDirection int direction, 369049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 370049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 371049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 372049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.left - dest.right; 373049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 374049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.left - source.right; 375049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 376049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.top - dest.bottom; 377049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 378049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.top - source.bottom; 379049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 380049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 381049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 382049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 383049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 384049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 385049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance along the major axis w.r.t the direction from 386049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * the edge of source to the far edge of dest. If the dest is 387049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * not in the direction from source, returns 1 to break ties 388049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * with {@link #majorAxisDistance}. 389049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 390049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction, 391049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 392049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest)); 393049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 394049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 395049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceToFarEdgeRaw( 396049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction, @NonNull Rect source, 397049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect dest) { 398049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 399049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 400049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.left - dest.left; 401049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 402049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.right - source.right; 403049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 404049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.top - dest.top; 405049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 406049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.bottom - source.bottom; 407049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 408049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 409049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 410049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 411049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 412049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 413049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Finds the distance on the minor axis w.r.t the direction to the 414049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * nearest edge of the destination rectangle. 415049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 416049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 417049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param source the source rect 418049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param dest the destination rect 419049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance 420049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 421049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source, 422049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect dest) { 423049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 424049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 425049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 426049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // the distance between the center verticals 427049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.abs( 4284d2c7b7c4f194034c5f17c4bee7320d808aabe4cAurimas Liutikas ((source.top + source.height() / 2) - ((dest.top + dest.height() / 2)))); 429049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 430049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 431049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // the distance between the center horizontals 432049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.abs( 433049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette ((source.left + source.width() / 2) - 434049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette ((dest.left + dest.width() / 2)))); 435049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 436049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 437049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 438049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 439049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 440049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 441049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Adapter used to obtain bounds from a generic data type. 442049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 443049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public interface BoundsAdapter<T> { 444049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette void obtainBounds(T data, Rect outBounds); 445049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 446049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 447049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 448049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Adapter used to obtain items from a generic collection type. 449049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 450049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public interface CollectionAdapter<T, V> { 451049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette V get(T collection, int index); 452049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette int size(T collection); 453049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 454049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette} 455