FocusFinderpublic 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 |
---|
boolean | beamBeats(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.
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));
| boolean | beamsOverlap(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?
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 View | findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas)Find the nearest touchable view to the specified view.
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 View | findNextFocus(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.
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 View | findNextFocus(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 View | findNextFocusFromRect(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.
return findNextFocus(root, null, focusedRect, direction);
| public static android.view.FocusFinder | getInstance()Get the focus finder for this thread.
return tlFocusFinder.get();
| int | getWeightedDistanceFor(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;
| boolean | isBetterCandidate(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.
// 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)));
| boolean | isCandidate(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}.");
| boolean | isToDirectionOf(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 boolean | isTouchCandidate(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 int | majorAxisDistance(int direction, android.graphics.Rect source, android.graphics.Rect dest)
return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
| static int | majorAxisDistanceRaw(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 int | majorAxisDistanceToFarEdge(int direction, android.graphics.Rect source, android.graphics.Rect dest)
return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
| static int | majorAxisDistanceToFarEdgeRaw(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 int | minorAxisDistance(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.
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}.");
|
|