PageRenderTime 28ms CodeModel.GetById 17ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/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
 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}