/widgets/access-shim/src/com/googlecode/eyesfree/widget/SelectionFinder.java

http://eyes-free.googlecode.com/ · 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. package com.googlecode.eyesfree.widget;
  17. import android.graphics.Rect;
  18. import android.view.View;
  19. import android.view.ViewGroup;
  20. import android.view.ViewParent;
  21. import android.widget.AbsListView;
  22. import android.widget.ListView;
  23. /**
  24. * @author alanv@google.com (Alan Viverette)
  25. */
  26. public class SelectionFinder {
  27. /** Used for hit rectangle calculations. */
  28. private static final Rect mTempFrame = new Rect();
  29. /**
  30. * Sets the current selection to be the {@link View} at the given
  31. * coordinate.
  32. *
  33. * @param currentSelection The currently selected {@link View}.
  34. * @param root The top-most {@link View} in the search space. Must be non-null.
  35. * @param rootX Root-relative X coordinate.
  36. * @param rootY Root-relative Y coordinate.
  37. */
  38. public static View getSelectionAtPoint(View currentSelection, View root, int rootX, int rootY) {
  39. if (root == null) {
  40. throw new IllegalArgumentException("Root view must be non-null.");
  41. }
  42. View searchView = currentSelection == null ? root : currentSelection;
  43. searchView.getGlobalVisibleRect(mTempFrame);
  44. // Adjust root-relative coordinates to be global.
  45. int globalX = rootX + root.getLeft() - root.getScrollX();
  46. int globalY = rootY + root.getTop() - root.getScrollY();
  47. // If the search rect doesn't contain the point, use its parent.
  48. while (!mTempFrame.contains(globalX, globalY)) {
  49. ViewParent parent = searchView.getParent();
  50. // If the parent isn't a View, we've exhaused the search space.
  51. if (parent instanceof View && parent != root.getParent()) {
  52. searchView = (View) parent;
  53. searchView.getGlobalVisibleRect(mTempFrame);
  54. } else {
  55. return null;
  56. }
  57. }
  58. // Adjust global coordinates to be selection-parent-relative.
  59. int localX = globalX - mTempFrame.left + searchView.getLeft();
  60. int localY = globalY - mTempFrame.top + searchView.getTop();
  61. // Search for selection from highest containing view.
  62. View selection = searchSelectionView(searchView, localX, localY);
  63. return selection;
  64. }
  65. /**
  66. * Finds the {@link View} within a {@link View} that contains the given
  67. * parent-relative coordinates. May return the supplied view.
  68. *
  69. * @param v The {@link View} to search.
  70. * @param parentX Parent-relative X coordinate.
  71. * @param parentY Parent-relative Y coordinate.
  72. * @return The {@link View} at (xf,xy) or <code>null</code> if none.
  73. */
  74. private static View searchSelectionView(View v, int parentX, int parentY) {
  75. // The view must be visible.
  76. if (v.getVisibility() != View.VISIBLE && v.getAnimation() == null) {
  77. return null;
  78. }
  79. v.getHitRect(mTempFrame);
  80. // The view must contain the point.
  81. if (!mTempFrame.contains(parentX, parentY)) {
  82. return null;
  83. }
  84. // Adjust to view-relative coordinates for child views.
  85. final int x = parentX - v.getLeft() + v.getScrollX();
  86. final int y = parentY - v.getTop() + v.getScrollY();
  87. // Delegate to class-specific search methods.
  88. if (v instanceof AbsListView) {
  89. return searchListFocusables((AbsListView) v, x, y);
  90. } else if (v instanceof ViewGroup) {
  91. return searchSelectionInGroup((ViewGroup) v, x, y);
  92. //} else if (v instanceof TextView) {
  93. // return v;
  94. } else if (v.isFocusable() || v.isClickable()) {
  95. return v;
  96. } else if (v.getParent() instanceof AbsListView) {
  97. AbsListView parent = (AbsListView) v.getParent();
  98. // List items aren't focusable, so if we try to search outward from
  99. // a currently selected list item then we'll end up here.
  100. if (parent.getPositionForView(v) != AbsListView.INVALID_POSITION) {
  101. return v;
  102. }
  103. }
  104. return null;
  105. }
  106. /**
  107. * Finds the {@link View} within a {@link ViewGroup} that contains the given
  108. * coordinates.
  109. *
  110. * @param v The {@link ViewGroup} to search.
  111. * @param x View-relative X coordinate.
  112. * @param y View-relative Y coordinate.
  113. * @return The {@link View} at (xf,xy) or <code>null</code> if none.
  114. */
  115. private static View searchSelectionInGroup(ViewGroup v, int x, int y) {
  116. int focusability = v.getDescendantFocusability();
  117. // TODO(alanv): This is necessary for things like widgets where the
  118. // TextViews should not be focusable, but messes with things like the
  119. // Launcher that handle focus events on their own.
  120. /* if (focusability == ViewGroup.FOCUS_BEFORE_DESCENDANTS) {
  121. return v.isFocusable() ? v : searchSelectionInGroupHelper(v, x, y);
  122. } else */
  123. if (focusability == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
  124. return v.isFocusable() ? v : null;
  125. } else {
  126. View focused = searchSelectionInGroupHelper(v, x, y);
  127. return focused != null ? focused : v.isFocusable() ? v : null;
  128. }
  129. }
  130. /**
  131. * Searches through a {@link ViewGroup}'s children to find the {@link View}
  132. * at the given coordinates.
  133. *
  134. * @param v The {@link ViewGroup} to search.
  135. * @param x View-relative X coordinate.
  136. * @param y View-relative Y coordinate.
  137. * @return The {@link View} at (xf,xy) or <code>null</code> if none.
  138. */
  139. private static View searchSelectionInGroupHelper(ViewGroup v, int x, int y) {
  140. View result = null;
  141. // Search within this view's children for a selection candidate.
  142. for (int i = v.getChildCount() - 1; result == null && i >= 0; i--) {
  143. result = searchSelectionView(v.getChildAt(i), x, y);
  144. }
  145. return result;
  146. }
  147. /**
  148. * Finds the {@link View} within an {@link AbsListView} that contains the
  149. * given coordinates.
  150. *
  151. * @param v The {@link AbsListView} to search.
  152. * @param x View-relative X coordinate.
  153. * @param y View-relative Y coordinate.
  154. * @return The {@link View} at (xf,xy) or <code>null</code> if none.
  155. */
  156. private static View searchListFocusables(AbsListView v, int x, int y) {
  157. final int position = v.pointToPosition(x, y);
  158. // If the view contains a child at (x, y) then return its view.
  159. if (position != ListView.INVALID_POSITION) {
  160. return v.getChildAt(position - v.getFirstVisiblePosition());
  161. }
  162. // Even if the ListView is focusable, we don't actually want to focus on it.
  163. return null;
  164. }
  165. }