/widgets/access-shim/src/com/googlecode/eyesfree/widget/SelectionFinder.java
Java | 193 lines | 79 code | 28 blank | 86 comment | 32 complexity | 712a966dea5b492d112f8abc61e8571c MD5 | raw file
1/* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.googlecode.eyesfree.widget; 18 19import android.graphics.Rect; 20import android.view.View; 21import android.view.ViewGroup; 22import android.view.ViewParent; 23import android.widget.AbsListView; 24import android.widget.ListView; 25 26/** 27 * @author alanv@google.com (Alan Viverette) 28 */ 29public class SelectionFinder { 30 /** Used for hit rectangle calculations. */ 31 private static final Rect mTempFrame = new Rect(); 32 33 /** 34 * Sets the current selection to be the {@link View} at the given 35 * coordinate. 36 * 37 * @param currentSelection The currently selected {@link View}. 38 * @param root The top-most {@link View} in the search space. Must be non-null. 39 * @param rootX Root-relative X coordinate. 40 * @param rootY Root-relative Y coordinate. 41 */ 42 public static View getSelectionAtPoint(View currentSelection, View root, int rootX, int rootY) { 43 if (root == null) { 44 throw new IllegalArgumentException("Root view must be non-null."); 45 } 46 47 View searchView = currentSelection == null ? root : currentSelection; 48 49 searchView.getGlobalVisibleRect(mTempFrame); 50 51 // Adjust root-relative coordinates to be global. 52 int globalX = rootX + root.getLeft() - root.getScrollX(); 53 int globalY = rootY + root.getTop() - root.getScrollY(); 54 55 // If the search rect doesn't contain the point, use its parent. 56 while (!mTempFrame.contains(globalX, globalY)) { 57 ViewParent parent = searchView.getParent(); 58 59 // If the parent isn't a View, we've exhaused the search space. 60 if (parent instanceof View && parent != root.getParent()) { 61 searchView = (View) parent; 62 searchView.getGlobalVisibleRect(mTempFrame); 63 } else { 64 return null; 65 } 66 } 67 68 // Adjust global coordinates to be selection-parent-relative. 69 int localX = globalX - mTempFrame.left + searchView.getLeft(); 70 int localY = globalY - mTempFrame.top + searchView.getTop(); 71 72 // Search for selection from highest containing view. 73 View selection = searchSelectionView(searchView, localX, localY); 74 75 return selection; 76 } 77 78 /** 79 * Finds the {@link View} within a {@link View} that contains the given 80 * parent-relative coordinates. May return the supplied view. 81 * 82 * @param v The {@link View} to search. 83 * @param parentX Parent-relative X coordinate. 84 * @param parentY Parent-relative Y coordinate. 85 * @return The {@link View} at (xf,xy) or <code>null</code> if none. 86 */ 87 private static View searchSelectionView(View v, int parentX, int parentY) { 88 // The view must be visible. 89 if (v.getVisibility() != View.VISIBLE && v.getAnimation() == null) { 90 return null; 91 } 92 93 v.getHitRect(mTempFrame); 94 95 // The view must contain the point. 96 if (!mTempFrame.contains(parentX, parentY)) { 97 return null; 98 } 99 100 // Adjust to view-relative coordinates for child views. 101 final int x = parentX - v.getLeft() + v.getScrollX(); 102 final int y = parentY - v.getTop() + v.getScrollY(); 103 104 // Delegate to class-specific search methods. 105 if (v instanceof AbsListView) { 106 return searchListFocusables((AbsListView) v, x, y); 107 } else if (v instanceof ViewGroup) { 108 return searchSelectionInGroup((ViewGroup) v, x, y); 109 //} else if (v instanceof TextView) { 110 // return v; 111 } else if (v.isFocusable() || v.isClickable()) { 112 return v; 113 } else if (v.getParent() instanceof AbsListView) { 114 AbsListView parent = (AbsListView) v.getParent(); 115 116 // List items aren't focusable, so if we try to search outward from 117 // a currently selected list item then we'll end up here. 118 if (parent.getPositionForView(v) != AbsListView.INVALID_POSITION) { 119 return v; 120 } 121 } 122 123 return null; 124 } 125 126 /** 127 * Finds the {@link View} within a {@link ViewGroup} that contains the given 128 * coordinates. 129 * 130 * @param v The {@link ViewGroup} to search. 131 * @param x View-relative X coordinate. 132 * @param y View-relative Y coordinate. 133 * @return The {@link View} at (xf,xy) or <code>null</code> if none. 134 */ 135 private static View searchSelectionInGroup(ViewGroup v, int x, int y) { 136 int focusability = v.getDescendantFocusability(); 137 138 // TODO(alanv): This is necessary for things like widgets where the 139 // TextViews should not be focusable, but messes with things like the 140 // Launcher that handle focus events on their own. 141 142 /* if (focusability == ViewGroup.FOCUS_BEFORE_DESCENDANTS) { 143 return v.isFocusable() ? v : searchSelectionInGroupHelper(v, x, y); 144 } else */ 145 if (focusability == ViewGroup.FOCUS_BLOCK_DESCENDANTS) { 146 return v.isFocusable() ? v : null; 147 } else { 148 View focused = searchSelectionInGroupHelper(v, x, y); 149 return focused != null ? focused : v.isFocusable() ? v : null; 150 } 151 } 152 153 /** 154 * Searches through a {@link ViewGroup}'s children to find the {@link View} 155 * at the given coordinates. 156 * 157 * @param v The {@link ViewGroup} to search. 158 * @param x View-relative X coordinate. 159 * @param y View-relative Y coordinate. 160 * @return The {@link View} at (xf,xy) or <code>null</code> if none. 161 */ 162 private static View searchSelectionInGroupHelper(ViewGroup v, int x, int y) { 163 View result = null; 164 165 // Search within this view's children for a selection candidate. 166 for (int i = v.getChildCount() - 1; result == null && i >= 0; i--) { 167 result = searchSelectionView(v.getChildAt(i), x, y); 168 } 169 170 return result; 171 } 172 173 /** 174 * Finds the {@link View} within an {@link AbsListView} that contains the 175 * given coordinates. 176 * 177 * @param v The {@link AbsListView} to search. 178 * @param x View-relative X coordinate. 179 * @param y View-relative Y coordinate. 180 * @return The {@link View} at (xf,xy) or <code>null</code> if none. 181 */ 182 private static View searchListFocusables(AbsListView v, int x, int y) { 183 final int position = v.pointToPosition(x, y); 184 185 // If the view contains a child at (x, y) then return its view. 186 if (position != ListView.INVALID_POSITION) { 187 return v.getChildAt(position - v.getFirstVisiblePosition()); 188 } 189 190 // Even if the ListView is focusable, we don't actually want to focus on it. 191 return null; 192 } 193}