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 { 36049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public static <L,T> T findNextFocusInRelativeDirection(@NonNull L focusables, 37049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @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 101049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) { 102049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mIsLayoutRtl = isLayoutRtl; 103049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter = adapter; 104049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 105049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 106049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public int compare(T first, T second) { 107049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect firstRect = mTemp1; 108049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect secondRect = mTemp2; 109049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 110049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter.obtainBounds(first, firstRect); 111049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette mAdapter.obtainBounds(second, secondRect); 112049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 113049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (firstRect.top < secondRect.top) { 114049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return -1; 115049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.top > secondRect.top) { 116049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 1; 117049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.left < secondRect.left) { 118049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? 1 : -1; 119049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.left > secondRect.left) { 120049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? -1 : 1; 121049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.bottom < secondRect.bottom) { 122049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return -1; 123049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.bottom > secondRect.bottom) { 124049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 1; 125049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.right < secondRect.right) { 126049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? 1 : -1; 127049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else if (firstRect.right > secondRect.right) { 128049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return mIsLayoutRtl ? -1 : 1; 129049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } else { 130049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // The view are distinct but completely coincident so we 131049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // consider them equal for our purposes. Since the sort is 132049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // stable, this means that the views will retain their 133049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // layout order relative to one another. 134049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 0; 135049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 136049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 137049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 138049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 139049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette public static <L,T> T findNextFocusInAbsoluteDirection(@NonNull L focusables, 140049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter, 141049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @Nullable T focused, @NonNull Rect focusedRect, int direction) { 142049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // Initialize the best candidate to something impossible so that 143049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // the first plausible view will become the best choice. 144049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect bestCandidateRect = new Rect(focusedRect); 145049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 146049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 147049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 148049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(focusedRect.width() + 1, 0); 149049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 150049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 151049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(-(focusedRect.width() + 1), 0); 152049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 153049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 154049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(0, focusedRect.height() + 1); 155049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 156049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 157049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.offset(0, -(focusedRect.height() + 1)); 158049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette break; 159049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette default: 160049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 161049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 162049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 163049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 164049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette T closest = null; 165049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 166049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int count = collectionAdapter.size(focusables); 167049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final Rect focusableRect = new Rect(); 168049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette for (int i = 0; i < count; i++) { 169049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final T focusable = collectionAdapter.get(focusables, i); 170049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (focusable == focused) { 171049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette continue; 172049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 173049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 174049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // get focus bounds of other view 175049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette adapter.obtainBounds(focusable, focusableRect); 176049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) { 177049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette bestCandidateRect.set(focusableRect); 178049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette closest = focusable; 179049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 180049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 181049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 182049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return closest; 183049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 184049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 185049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 186049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Is candidate a better candidate than currentBest for a focus search 187049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * in a particular direction from a source rect? This is the core 188049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * routine that determines the order of focus searching. 189049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 190049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 191049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param source the source from which we are searching 192049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param candidate the candidate rectangle 193049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param currentBest the current best rectangle 194049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return {@code true} if the candidate rectangle is a better than the 195049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * current best rectangle, {@code false} otherwise 196049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 197049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isBetterCandidate( 198049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction, @NonNull Rect source, 199049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect candidate, @NonNull Rect currentBest) { 200049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // To be a better candidate, need to at least be a candidate in the 201049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // first place. :) 202049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isCandidate(source, candidate, direction)) { 203049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 204049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 205049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 206049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // We know that candidateRect is a candidate. If currentBest is not 207049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // a candidate, candidateRect is better. 208049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isCandidate(source, currentBest, direction)) { 209049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 210049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 211049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 212049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If candidateRect is better by beam, it wins. 213049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (beamBeats(direction, source, candidate, currentBest)) { 214049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 215049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 216049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 217049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If currentBest is better, then candidateRect cant' be. :) 218049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (beamBeats(direction, source, currentBest, candidate)) { 219049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 220049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 221049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 222049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // Otherwise, do fudge-tastic comparison of the major and minor 223049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // axis. 224049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int candidateDist = getWeightedDistanceFor( 225049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette majorAxisDistance(direction, source, candidate), 226049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette minorAxisDistance(direction, source, candidate)); 227049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final int currentBestDist = getWeightedDistanceFor( 228049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette majorAxisDistance(direction, source, currentBest), 229049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette minorAxisDistance(direction, source, currentBest)); 230049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return candidateDist < currentBestDist; 231049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 232049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 233049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 234049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * One rectangle may be another candidate than another by virtue of 235049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * being exclusively in the beam of the source rect. 236049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 237049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return whether rect1 is a better candidate than rect2 by virtue of 238049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * it being in source's beam 239049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 240049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean beamBeats(@FocusRealDirection int direction, 241049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) { 242049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); 243049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2); 244049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 245049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If rect1 isn't exclusively in the src beam, it doesn't win. 246049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (rect2InSrcBeam || !rect1InSrcBeam) { 247049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return false; 248049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 249049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 250049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // We know rect1 is in the beam, and rect2 is not. 251049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 252049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // If rect1 is to the direction of, and rect2 is not, rect1 wins. 253049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For example, for direction left, if rect1 is to the left of the 254049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // source and rect2 is below, then we always prefer the in beam 255049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // rect1, since rect2 could be reached by going down. 256049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (!isToDirectionOf(direction, source, rect2)) { 257049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 258049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 259049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 260049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For horizontal directions, being exclusively in beam always 261049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // wins. 262049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) { 263049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return true; 264049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 265049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 266049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // For vertical directions, beams only beat up to a point: now, as 267049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // long as rect2 isn't completely closer, rect1 wins, e.g. for 268049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // direction down, completely closer means for rect2's top edge to 269049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // be closer to the source's top edge than rect1's bottom edge. 270049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return majorAxisDistance(direction, source, rect1) 271049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette < majorAxisDistanceToFarEdge(direction, source, rect2); 272049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 273049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 274049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 275049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Fudge-factor opportunity: how to calculate distance given major and 276049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * minor axis distances. 277049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * <p/> 278049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Warning: this fudge factor is finely tuned, be sure to run all focus 279049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * tests if you dare tweak it. 280049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 281049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) { 282049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return 13 * majorAxisDistance * majorAxisDistance 283049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + minorAxisDistance * minorAxisDistance; 284049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 285049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 286049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 287049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Is destRect a candidate for the next focus given the direction? This 288049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * checks whether the dest is at least partially to the direction of 289049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * (e.g. left of) from source. 290049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * <p/> 291049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Includes an edge case for an empty rect,which is used in some cases 292049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * when searching from a point on the screen. 293049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 294049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect, 295049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction) { 296049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 297049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 298049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.right > destRect.right || srcRect.left >= destRect.right) 299049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.left > destRect.left; 300049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 301049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.left < destRect.left || srcRect.right <= destRect.left) 302049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.right < destRect.right; 303049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 304049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) 305049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.top > destRect.top; 306049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 307049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) 308049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette && srcRect.bottom < destRect.bottom; 309049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 310049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 311049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 312049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 313049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 314049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 315049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 316049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap? 317049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 318049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 319049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param rect1 the first rectangle 320049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param rect2 the second rectangle 321049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return whether the beams overlap 322049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 323049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean beamsOverlap(@FocusRealDirection int direction, 324049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect rect1, @NonNull Rect rect2) { 325049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 326049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 327049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 328049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom); 329049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 330049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 331049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return (rect2.right >= rect1.left) && (rect2.left <= rect1.right); 332049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 333049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 334049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 335049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 336049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 337049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 338049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * e.g for left, is 'to left of' 339049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 340049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static boolean isToDirectionOf(@FocusRealDirection int direction, 341049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect src, @NonNull Rect dest) { 342049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 343049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 344049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.left >= dest.right; 345049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 346049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.right <= dest.left; 347049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 348049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.top >= dest.bottom; 349049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 350049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return src.bottom <= dest.top; 351049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 352049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 353049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 354049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 355049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 356049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 357049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance from the edge furthest in the given direction 358049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * of source to the edge nearest in the given direction of 359049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * dest. If the dest is not in the direction from source, 360049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * returns 0. 361049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 362049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistance(@FocusRealDirection int direction, 363049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 364049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.max(0, majorAxisDistanceRaw(direction, source, dest)); 365049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 366049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 367049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceRaw(@FocusRealDirection int direction, 368049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 369049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 370049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 371049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.left - dest.right; 372049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 373049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.left - source.right; 374049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 375049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.top - dest.bottom; 376049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 377049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.top - source.bottom; 378049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 379049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 380049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 381049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 382049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 383049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 384049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance along the major axis w.r.t the direction from 385049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * the edge of source to the far edge of dest. If the dest is 386049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * not in the direction from source, returns 1 to break ties 387049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * with {@link #majorAxisDistance}. 388049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 389049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction, 390049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect source, @NonNull Rect dest) { 391049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest)); 392049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 393049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 394049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int majorAxisDistanceToFarEdgeRaw( 395049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @FocusRealDirection int direction, @NonNull Rect source, 396049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect dest) { 397049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 398049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 399049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.left - dest.left; 400049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 401049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.right - source.right; 402049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_UP: 403049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return source.top - dest.top; 404049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_DOWN: 405049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return dest.bottom - source.bottom; 406049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 407049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette throw new IllegalArgumentException("direction must be one of " 408049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); 409049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette } 410049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette 411049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette /** 412049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * Finds the distance on the minor axis w.r.t the direction to the 413049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * nearest edge of the destination rectangle. 414049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * 415049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param direction the direction (up, down, left, right) 416049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param source the source rect 417049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @param dest the destination rect 418049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette * @return the distance 419049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette */ 420049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source, 421049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette @NonNull Rect dest) { 422049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette switch (direction) { 423049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_LEFT: 424049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette case View.FOCUS_RIGHT: 425049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette // the distance between the center verticals 426049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette return Math.abs( 427049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette ((source.top + source.height() / 2) - 428049a692ac952be43d5fcd975ec6d394ca058c9d8Alan Viverette ((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