FileDocCategorySizeDatePackage
FocusFinder.javaAPI DocAndroid 1.5 API19095Wed May 06 22:41:56 BST 2009android.view

FocusFinder

public class FocusFinder extends Object
The algorithm used for finding the next focusable view in a given direction from a view that currently has focus.

Fields Summary
private static ThreadLocal
tlFocusFinder
android.graphics.Rect
mFocusedRect
android.graphics.Rect
mOtherRect
android.graphics.Rect
mBestCandidateRect
Constructors Summary
private FocusFinder()


    // enforce thread local access
      
Methods Summary
booleanbeamBeats(int direction, android.graphics.Rect source, android.graphics.Rect rect1, android.graphics.Rect rect2)
One rectangle may be another candidate than another by virtue of being exclusively in the beam of the source rect.

return
Whether rect1 is a better candidate than rect2 by virtue of it being in src's beam

        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);

        // if rect1 isn't exclusively in the src beam, it doesn't win
        if (rect2InSrcBeam || !rect1InSrcBeam) {
            return false;
        }

        // we know rect1 is in the beam, and rect2 is not

        // if rect1 is to the direction of, and rect2 is not, rect1 wins.
        // for example, for direction left, if rect1 is to the left of the source
        // and rect2 is below, then we always prefer the in beam rect1, since rect2
        // could be reached by going down.
        if (!isToDirectionOf(direction, source, rect2)) {
            return true;
        }

        // for horizontal directions, being exclusively in beam always wins
        if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
            return true;
        }        

        // for vertical directions, beams only beat up to a point:
        // now, as long as rect2 isn't completely closer, rect1 wins
        // e.g for direction down, completely closer means for rect2's top
        // edge to be closer to the source's top edge than rect1's bottom edge.
        return (majorAxisDistance(direction, source, rect1)
                < majorAxisDistanceToFarEdge(direction, source, rect2));
    
booleanbeamsOverlap(int direction, android.graphics.Rect rect1, android.graphics.Rect rect2)
Do the "beams" w.r.t the given direcition's axos of rect1 and rect2 overlap?

param
direction the direction (up, down, left, right)
param
rect1 The first rectangle
param
rect2 The second rectangle
return
whether the beams overlap

        switch (direction) {
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
                return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
public ViewfindNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas)
Find the nearest touchable view to the specified view.

param
root The root of the tree in which to search
param
x X coordinate from which to start the search
param
y Y coordinate from which to start the search
param
direction Direction to look
param
deltas Offset from the to the edge of the nearest view. Note that this array may already be populated with values.
return
The nearest touchable view, or null if none exists.

        ArrayList<View> touchables = root.getTouchables();
        int minDistance = Integer.MAX_VALUE;
        View closest = null;

        int numTouchables = touchables.size();
        
        int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
        
        Rect closestBounds = new Rect();
        Rect touchableBounds = mOtherRect;
        
        for (int i = 0; i < numTouchables; i++) {
            View touchable = touchables.get(i);

            // get visible bounds of other view in same coordinate system
            touchable.getDrawingRect(touchableBounds);
            
            root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);

            if (!isTouchCandidate(x, y, touchableBounds, direction)) {
                continue;
            }

            int distance = Integer.MAX_VALUE;

            switch (direction) {
            case View.FOCUS_LEFT:
                distance = x - touchableBounds.right + 1;
                break;
            case View.FOCUS_RIGHT:
                distance = touchableBounds.left;
                break;
            case View.FOCUS_UP:
                distance = y - touchableBounds.bottom + 1;
                break;
            case View.FOCUS_DOWN:
                distance = touchableBounds.top;
                break;
            }

            if (distance < edgeSlop) {
                // Give preference to innermost views
                if (closest == null ||
                        closestBounds.contains(touchableBounds) ||
                        (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
                    minDistance = distance;
                    closest = touchable;
                    closestBounds.set(touchableBounds);
                    switch (direction) {
                    case View.FOCUS_LEFT:
                        deltas[0] = -distance;
                        break;
                    case View.FOCUS_RIGHT:
                        deltas[0] = distance;
                        break;
                    case View.FOCUS_UP:
                        deltas[1] = -distance;
                        break;
                    case View.FOCUS_DOWN:
                        deltas[1] = distance;
                        break;
                    }
                }
            }
        }
        return closest;
    
public final ViewfindNextFocus(ViewGroup root, View focused, int direction)
Find the next view to take focus in root's descendants, starting from the view that currently is focused.

param
root Contains focused
param
focused Has focus now.
param
direction Direction to look.
return
The next focusable view, or null if none exists.


        if (focused != null) {
            // check for user specified next focus
            View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
            if (userSetNextFocus != null &&
                userSetNextFocus.isFocusable() &&
                (!userSetNextFocus.isInTouchMode() ||
                 userSetNextFocus.isFocusableInTouchMode())) {
                return userSetNextFocus;
            }

            // fill in interesting rect from focused
            focused.getFocusedRect(mFocusedRect);
            root.offsetDescendantRectToMyCoords(focused, mFocusedRect);
        } else {
            // make up a rect at top left or bottom right of root
            switch (direction) {
                case View.FOCUS_RIGHT:
                case View.FOCUS_DOWN:
                    final int rootTop = root.getScrollY();
                    final int rootLeft = root.getScrollX();
                    mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
                    break;

                case View.FOCUS_LEFT:
                case View.FOCUS_UP:
                    final int rootBottom = root.getScrollY() + root.getHeight();
                    final int rootRight = root.getScrollX() + root.getWidth();
                    mFocusedRect.set(rootRight, rootBottom,
                            rootRight, rootBottom);
                    break;
            }
        }
        return findNextFocus(root, focused, mFocusedRect, direction);
    
private ViewfindNextFocus(ViewGroup root, View focused, android.graphics.Rect focusedRect, int direction)

        ArrayList<View> focusables = root.getFocusables(direction);

        // initialize the best candidate to something impossible
        // (so the first plausible view will become the best choice)
        mBestCandidateRect.set(focusedRect);
        switch(direction) {
            case View.FOCUS_LEFT:
                mBestCandidateRect.offset(focusedRect.width() + 1, 0);
                break;
            case View.FOCUS_RIGHT:
                mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
                break;
            case View.FOCUS_UP:
                mBestCandidateRect.offset(0, focusedRect.height() + 1);
                break;
            case View.FOCUS_DOWN:
                mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
        }

        View closest = null;

        int numFocusables = focusables.size();
        for (int i = 0; i < numFocusables; i++) {
            View focusable = focusables.get(i);

            // only interested in other non-root views
            if (focusable == focused || focusable == root) continue;

            // get visible bounds of other view in same coordinate system
            focusable.getDrawingRect(mOtherRect);
            root.offsetDescendantRectToMyCoords(focusable, mOtherRect);

            if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
                mBestCandidateRect.set(mOtherRect);
                closest = focusable;
            }
        }
        return closest;
    
public ViewfindNextFocusFromRect(ViewGroup root, android.graphics.Rect focusedRect, int direction)
Find the next view to take focus in root's descendants, searching from a particular rectangle in root's coordinates.

param
root Contains focusedRect.
param
focusedRect The starting point of the search.
param
direction Direction to look.
return
The next focusable view, or null if none exists.

        return findNextFocus(root, null, focusedRect, direction);
    
public static android.view.FocusFindergetInstance()
Get the focus finder for this thread.


                
        
        return tlFocusFinder.get();
    
intgetWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)
Fudge-factor opportunity: how to calculate distance given major and minor axis distances. Warning: this fudge factor is finely tuned, be sure to run all focus tests if you dare tweak it.

        return 13 * majorAxisDistance * majorAxisDistance
                + minorAxisDistance * minorAxisDistance;
    
booleanisBetterCandidate(int direction, android.graphics.Rect source, android.graphics.Rect rect1, android.graphics.Rect rect2)
Is rect1 a better candidate than rect2 for a focus search in a particular direction from a source rect? This is the core routine that determines the order of focus searching.

param
direction the direction (up, down, left, right)
param
source The source we are searching from
param
rect1 The candidate rectangle
param
rect2 The current best candidate.
return
Whether the candidate is the new best.


        // to be a better candidate, need to at least be a candidate in the first
        // place :)
        if (!isCandidate(source, rect1, direction)) {
            return false;
        }

        // we know that rect1 is a candidate.. if rect2 is not a candidate,
        // rect1 is better
        if (!isCandidate(source, rect2, direction)) {
            return true;
        }

        // if rect1 is better by beam, it wins
        if (beamBeats(direction, source, rect1, rect2)) {
            return true;
        }

        // if rect2 is better, then rect1 cant' be :)
        if (beamBeats(direction, source, rect2, rect1)) {
            return false;
        }

        // otherwise, do fudge-tastic comparison of the major and minor axis
        return (getWeightedDistanceFor(
                        majorAxisDistance(direction, source, rect1),
                        minorAxisDistance(direction, source, rect1))
                < getWeightedDistanceFor(
                        majorAxisDistance(direction, source, rect2),
                        minorAxisDistance(direction, source, rect2)));
    
booleanisCandidate(android.graphics.Rect srcRect, android.graphics.Rect destRect, int direction)
Is destRect a candidate for the next focus given the direction? This checks whether the dest is at least partially to the direction of (e.g left of) from source. Includes an edge case for an empty rect (which is used in some cases when searching from a point on the screen).

        switch (direction) {
            case View.FOCUS_LEFT:
                return (srcRect.right > destRect.right || srcRect.left >= destRect.right) 
                        && srcRect.left > destRect.left;
            case View.FOCUS_RIGHT:
                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
                        && srcRect.right < destRect.right;
            case View.FOCUS_UP:
                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
                        && srcRect.top > destRect.top;
            case View.FOCUS_DOWN:
                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
                        && srcRect.bottom < destRect.bottom;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
booleanisToDirectionOf(int direction, android.graphics.Rect src, android.graphics.Rect dest)
e.g for left, is 'to left of'

        switch (direction) {
            case View.FOCUS_LEFT:
                return src.left >= dest.right;
            case View.FOCUS_RIGHT:
                return src.right <= dest.left;
            case View.FOCUS_UP:
                return src.top >= dest.bottom;
            case View.FOCUS_DOWN:
                return src.bottom <= dest.top;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
private booleanisTouchCandidate(int x, int y, android.graphics.Rect destRect, int direction)
Is destRect a candidate for the next touch given the direction?

        switch (direction) {
            case View.FOCUS_LEFT:
                return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
            case View.FOCUS_RIGHT:
                return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
            case View.FOCUS_UP:
                return destRect.top <= y && destRect.left <= x && x <= destRect.right;
            case View.FOCUS_DOWN:
                return destRect.top >= y && destRect.left <= x && x <= destRect.right;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
static intmajorAxisDistance(int direction, android.graphics.Rect source, android.graphics.Rect dest)

return
The distance from the edge furthest in the given direction of source to the edge nearest in the given direction of dest. If the dest is not in the direction from source, return 0.

        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
    
static intmajorAxisDistanceRaw(int direction, android.graphics.Rect source, android.graphics.Rect dest)

        switch (direction) {
            case View.FOCUS_LEFT:
                return source.left - dest.right;
            case View.FOCUS_RIGHT:
                return dest.left - source.right;
            case View.FOCUS_UP:
                return source.top - dest.bottom;
            case View.FOCUS_DOWN:
                return dest.top - source.bottom;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
static intmajorAxisDistanceToFarEdge(int direction, android.graphics.Rect source, android.graphics.Rect dest)

return
The distance along the major axis w.r.t the direction from the edge of source to the far edge of dest. If the dest is not in the direction from source, return 1 (to break ties with {@link #majorAxisDistance}).

        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
    
static intmajorAxisDistanceToFarEdgeRaw(int direction, android.graphics.Rect source, android.graphics.Rect dest)

        switch (direction) {
            case View.FOCUS_LEFT:
                return source.left - dest.left;
            case View.FOCUS_RIGHT:
                return dest.right - source.right;
            case View.FOCUS_UP:
                return source.top - dest.top;
            case View.FOCUS_DOWN:
                return dest.bottom - source.bottom;
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    
static intminorAxisDistance(int direction, android.graphics.Rect source, android.graphics.Rect dest)
Find the distance on the minor axis w.r.t the direction to the nearest edge of the destination rectange.

param
direction the direction (up, down, left, right)
param
source The source rect.
param
dest The destination rect.
return
The distance.

        switch (direction) {
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                // the distance between the center verticals
                return Math.abs(
                        ((source.top + source.height() / 2) -
                        ((dest.top + dest.height() / 2))));
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
                // the distance between the center horizontals
                return Math.abs(
                        ((source.left + source.width() / 2) -
                        ((dest.left + dest.width() / 2))));
        }
        throw new IllegalArgumentException("direction must be one of "
                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");