Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import android.graphics.Rect;
     20 
     21 import java.util.ArrayList;
     22 import java.util.Collections;
     23 import java.util.Comparator;
     24 
     25 /**
     26  * The algorithm used for finding the next focusable view in a given direction
     27  * from a view that currently has focus.
     28  */
     29 public class FocusFinder {
     30 
     31     private static final ThreadLocal<FocusFinder> tlFocusFinder =
     32             new ThreadLocal<FocusFinder>() {
     33                 @Override
     34                 protected FocusFinder initialValue() {
     35                     return new FocusFinder();
     36                 }
     37             };
     38 
     39     /**
     40      * Get the focus finder for this thread.
     41      */
     42     public static FocusFinder getInstance() {
     43         return tlFocusFinder.get();
     44     }
     45 
     46     final Rect mFocusedRect = new Rect();
     47     final Rect mOtherRect = new Rect();
     48     final Rect mBestCandidateRect = new Rect();
     49     final SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
     50 
     51     private final ArrayList<View> mTempList = new ArrayList<View>();
     52 
     53     // enforce thread local access
     54     private FocusFinder() {}
     55 
     56     /**
     57      * Find the next view to take focus in root's descendants, starting from the view
     58      * that currently is focused.
     59      * @param root Contains focused. Cannot be null.
     60      * @param focused Has focus now.
     61      * @param direction Direction to look.
     62      * @return The next focusable view, or null if none exists.
     63      */
     64     public final View findNextFocus(ViewGroup root, View focused, int direction) {
     65         return findNextFocus(root, focused, null, direction);
     66     }
     67 
     68     /**
     69      * Find the next view to take focus in root's descendants, searching from
     70      * a particular rectangle in root's coordinates.
     71      * @param root Contains focusedRect. Cannot be null.
     72      * @param focusedRect The starting point of the search.
     73      * @param direction Direction to look.
     74      * @return The next focusable view, or null if none exists.
     75      */
     76     public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
     77         mFocusedRect.set(focusedRect);
     78         return findNextFocus(root, null, mFocusedRect, direction);
     79     }
     80 
     81     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
     82         if ((direction & View.FOCUS_ACCESSIBILITY) != View.FOCUS_ACCESSIBILITY) {
     83             return findNextInputFocus(root, focused, focusedRect, direction);
     84         } else {
     85             return findNextAccessibilityFocus(root, focused, focusedRect, direction);
     86         }
     87     }
     88 
     89     private View findNextInputFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
     90         View next = null;
     91         if (focused != null) {
     92             next = findNextUserSpecifiedInputFocus(root, focused, direction);
     93         }
     94         if (next != null) {
     95             return next;
     96         }
     97         ArrayList<View> focusables = mTempList;
     98         try {
     99             focusables.clear();
    100             root.addFocusables(focusables, direction);
    101             if (!focusables.isEmpty()) {
    102                 next = findNextFocus(root, focused, focusedRect, direction, focusables);
    103             }
    104         } finally {
    105             focusables.clear();
    106         }
    107         return next;
    108     }
    109 
    110     private View findNextUserSpecifiedInputFocus(ViewGroup root, View focused, int direction) {
    111         // check for user specified next focus
    112         View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
    113         if (userSetNextFocus != null && userSetNextFocus.isFocusable()
    114                 && (!userSetNextFocus.isInTouchMode()
    115                         || userSetNextFocus.isFocusableInTouchMode())) {
    116             return userSetNextFocus;
    117         }
    118         return null;
    119     }
    120 
    121     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
    122             int direction, ArrayList<View> focusables) {
    123         final int directionMasked = (direction & ~View.FOCUS_ACCESSIBILITY);
    124         if (focused != null) {
    125             if (focusedRect == null) {
    126                 focusedRect = mFocusedRect;
    127             }
    128             // fill in interesting rect from focused
    129             focused.getFocusedRect(focusedRect);
    130             root.offsetDescendantRectToMyCoords(focused, focusedRect);
    131         } else {
    132             if (focusedRect == null) {
    133                 focusedRect = mFocusedRect;
    134                 // make up a rect at top left or bottom right of root
    135                 switch (directionMasked) {
    136                     case View.FOCUS_RIGHT:
    137                     case View.FOCUS_DOWN:
    138                         setFocusTopLeft(root, focusedRect);
    139                         break;
    140                     case View.FOCUS_FORWARD:
    141                         if (root.isLayoutRtl()) {
    142                             setFocusBottomRight(root, focusedRect);
    143                         } else {
    144                             setFocusTopLeft(root, focusedRect);
    145                         }
    146                         break;
    147 
    148                     case View.FOCUS_LEFT:
    149                     case View.FOCUS_UP:
    150                         setFocusBottomRight(root, focusedRect);
    151                         break;
    152                     case View.FOCUS_BACKWARD:
    153                         if (root.isLayoutRtl()) {
    154                             setFocusTopLeft(root, focusedRect);
    155                         } else {
    156                             setFocusBottomRight(root, focusedRect);
    157                         break;
    158                     }
    159                 }
    160             }
    161         }
    162 
    163         switch (directionMasked) {
    164             case View.FOCUS_FORWARD:
    165             case View.FOCUS_BACKWARD:
    166                 return findNextInputFocusInRelativeDirection(focusables, root, focused, focusedRect,
    167                         directionMasked);
    168             case View.FOCUS_UP:
    169             case View.FOCUS_DOWN:
    170             case View.FOCUS_LEFT:
    171             case View.FOCUS_RIGHT:
    172                 return findNextInputFocusInAbsoluteDirection(focusables, root, focused,
    173                         focusedRect, directionMasked);
    174             default:
    175                 throw new IllegalArgumentException("Unknown direction: " + directionMasked);
    176         }
    177     }
    178 
    179     private View findNextAccessibilityFocus(ViewGroup root, View focused,
    180             Rect focusedRect, int direction) {
    181         ArrayList<View> focusables = mTempList;
    182         try {
    183             focusables.clear();
    184             root.addFocusables(focusables, direction, View.FOCUSABLES_ACCESSIBILITY);
    185             View next = findNextFocus(root, focused, focusedRect, direction,
    186                     focusables);
    187             return next;
    188         } finally {
    189             focusables.clear();
    190         }
    191     }
    192 
    193     private View findNextInputFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
    194             View focused, Rect focusedRect, int direction) {
    195         try {
    196             // Note: This sort is stable.
    197             mSequentialFocusComparator.setRoot(root);
    198             Collections.sort(focusables, mSequentialFocusComparator);
    199         } finally {
    200             mSequentialFocusComparator.recycle();
    201         }
    202 
    203         final int count = focusables.size();
    204         switch (direction) {
    205             case View.FOCUS_FORWARD:
    206                 return getForwardFocusable(root, focused, focusables, count);
    207             case View.FOCUS_BACKWARD:
    208                 return getBackwardFocusable(root, focused, focusables, count);
    209         }
    210         return focusables.get(count - 1);
    211     }
    212 
    213     private void setFocusBottomRight(ViewGroup root, Rect focusedRect) {
    214         final int rootBottom = root.getScrollY() + root.getHeight();
    215         final int rootRight = root.getScrollX() + root.getWidth();
    216         focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
    217     }
    218 
    219     private void setFocusTopLeft(ViewGroup root, Rect focusedRect) {
    220         final int rootTop = root.getScrollY();
    221         final int rootLeft = root.getScrollX();
    222         focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
    223     }
    224 
    225     View findNextInputFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
    226             Rect focusedRect, int direction) {
    227         // initialize the best candidate to something impossible
    228         // (so the first plausible view will become the best choice)
    229         mBestCandidateRect.set(focusedRect);
    230         switch(direction) {
    231             case View.FOCUS_LEFT:
    232                 mBestCandidateRect.offset(focusedRect.width() + 1, 0);
    233                 break;
    234             case View.FOCUS_RIGHT:
    235                 mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
    236                 break;
    237             case View.FOCUS_UP:
    238                 mBestCandidateRect.offset(0, focusedRect.height() + 1);
    239                 break;
    240             case View.FOCUS_DOWN:
    241                 mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
    242         }
    243 
    244         View closest = null;
    245 
    246         int numFocusables = focusables.size();
    247         for (int i = 0; i < numFocusables; i++) {
    248             View focusable = focusables.get(i);
    249 
    250             // only interested in other non-root views
    251             if (focusable == focused || focusable == root) continue;
    252 
    253             // get visible bounds of other view in same coordinate system
    254             focusable.getDrawingRect(mOtherRect);
    255             root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
    256 
    257             if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
    258                 mBestCandidateRect.set(mOtherRect);
    259                 closest = focusable;
    260             }
    261         }
    262         return closest;
    263     }
    264 
    265     private static View getForwardFocusable(ViewGroup root, View focused,
    266                                             ArrayList<View> focusables, int count) {
    267         return (root.isLayoutRtl()) ?
    268                 getPreviousFocusable(focused, focusables, count) :
    269                 getNextFocusable(focused, focusables, count);
    270     }
    271 
    272     private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
    273         if (focused != null) {
    274             int position = focusables.lastIndexOf(focused);
    275             if (position >= 0 && position + 1 < count) {
    276                 return focusables.get(position + 1);
    277             }
    278         }
    279         if (!focusables.isEmpty()) {
    280             return focusables.get(0);
    281         }
    282         return null;
    283     }
    284 
    285     private static View getBackwardFocusable(ViewGroup root, View focused,
    286                                              ArrayList<View> focusables, int count) {
    287         return (root.isLayoutRtl()) ?
    288                 getNextFocusable(focused, focusables, count) :
    289                 getPreviousFocusable(focused, focusables, count);
    290     }
    291 
    292     private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
    293         if (focused != null) {
    294             int position = focusables.indexOf(focused);
    295             if (position > 0) {
    296                 return focusables.get(position - 1);
    297             }
    298         }
    299         if (!focusables.isEmpty()) {
    300             return focusables.get(count - 1);
    301         }
    302         return null;
    303     }
    304 
    305     /**
    306      * Is rect1 a better candidate than rect2 for a focus search in a particular
    307      * direction from a source rect?  This is the core routine that determines
    308      * the order of focus searching.
    309      * @param direction the direction (up, down, left, right)
    310      * @param source The source we are searching from
    311      * @param rect1 The candidate rectangle
    312      * @param rect2 The current best candidate.
    313      * @return Whether the candidate is the new best.
    314      */
    315     boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {
    316 
    317         // to be a better candidate, need to at least be a candidate in the first
    318         // place :)
    319         if (!isCandidate(source, rect1, direction)) {
    320             return false;
    321         }
    322 
    323         // we know that rect1 is a candidate.. if rect2 is not a candidate,
    324         // rect1 is better
    325         if (!isCandidate(source, rect2, direction)) {
    326             return true;
    327         }
    328 
    329         // if rect1 is better by beam, it wins
    330         if (beamBeats(direction, source, rect1, rect2)) {
    331             return true;
    332         }
    333 
    334         // if rect2 is better, then rect1 cant' be :)
    335         if (beamBeats(direction, source, rect2, rect1)) {
    336             return false;
    337         }
    338 
    339         // otherwise, do fudge-tastic comparison of the major and minor axis
    340         return (getWeightedDistanceFor(
    341                         majorAxisDistance(direction, source, rect1),
    342                         minorAxisDistance(direction, source, rect1))
    343                 < getWeightedDistanceFor(
    344                         majorAxisDistance(direction, source, rect2),
    345                         minorAxisDistance(direction, source, rect2)));
    346     }
    347 
    348     /**
    349      * One rectangle may be another candidate than another by virtue of being
    350      * exclusively in the beam of the source rect.
    351      * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's
    352      *      beam
    353      */
    354     boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
    355         final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
    356         final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
    357 
    358         // if rect1 isn't exclusively in the src beam, it doesn't win
    359         if (rect2InSrcBeam || !rect1InSrcBeam) {
    360             return false;
    361         }
    362 
    363         // we know rect1 is in the beam, and rect2 is not
    364 
    365         // if rect1 is to the direction of, and rect2 is not, rect1 wins.
    366         // for example, for direction left, if rect1 is to the left of the source
    367         // and rect2 is below, then we always prefer the in beam rect1, since rect2
    368         // could be reached by going down.
    369         if (!isToDirectionOf(direction, source, rect2)) {
    370             return true;
    371         }
    372 
    373         // for horizontal directions, being exclusively in beam always wins
    374         if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
    375             return true;
    376         }
    377 
    378         // for vertical directions, beams only beat up to a point:
    379         // now, as long as rect2 isn't completely closer, rect1 wins
    380         // e.g for direction down, completely closer means for rect2's top
    381         // edge to be closer to the source's top edge than rect1's bottom edge.
    382         return (majorAxisDistance(direction, source, rect1)
    383                 < majorAxisDistanceToFarEdge(direction, source, rect2));
    384     }
    385 
    386     /**
    387      * Fudge-factor opportunity: how to calculate distance given major and minor
    388      * axis distances.  Warning: this fudge factor is finely tuned, be sure to
    389      * run all focus tests if you dare tweak it.
    390      */
    391     int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
    392         return 13 * majorAxisDistance * majorAxisDistance
    393                 + minorAxisDistance * minorAxisDistance;
    394     }
    395 
    396     /**
    397      * Is destRect a candidate for the next focus given the direction?  This
    398      * checks whether the dest is at least partially to the direction of (e.g left of)
    399      * from source.
    400      *
    401      * Includes an edge case for an empty rect (which is used in some cases when
    402      * searching from a point on the screen).
    403      */
    404     boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
    405         switch (direction) {
    406             case View.FOCUS_LEFT:
    407                 return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
    408                         && srcRect.left > destRect.left;
    409             case View.FOCUS_RIGHT:
    410                 return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
    411                         && srcRect.right < destRect.right;
    412             case View.FOCUS_UP:
    413                 return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
    414                         && srcRect.top > destRect.top;
    415             case View.FOCUS_DOWN:
    416                 return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
    417                         && srcRect.bottom < destRect.bottom;
    418         }
    419         throw new IllegalArgumentException("direction must be one of "
    420                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    421     }
    422 
    423 
    424     /**
    425      * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
    426      * @param direction the direction (up, down, left, right)
    427      * @param rect1 The first rectangle
    428      * @param rect2 The second rectangle
    429      * @return whether the beams overlap
    430      */
    431     boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {
    432         switch (direction) {
    433             case View.FOCUS_LEFT:
    434             case View.FOCUS_RIGHT:
    435                 return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
    436             case View.FOCUS_UP:
    437             case View.FOCUS_DOWN:
    438                 return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
    439         }
    440         throw new IllegalArgumentException("direction must be one of "
    441                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    442     }
    443 
    444     /**
    445      * e.g for left, is 'to left of'
    446      */
    447     boolean isToDirectionOf(int direction, Rect src, Rect dest) {
    448         switch (direction) {
    449             case View.FOCUS_LEFT:
    450                 return src.left >= dest.right;
    451             case View.FOCUS_RIGHT:
    452                 return src.right <= dest.left;
    453             case View.FOCUS_UP:
    454                 return src.top >= dest.bottom;
    455             case View.FOCUS_DOWN:
    456                 return src.bottom <= dest.top;
    457         }
    458         throw new IllegalArgumentException("direction must be one of "
    459                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    460     }
    461 
    462     /**
    463      * @return The distance from the edge furthest in the given direction
    464      *   of source to the edge nearest in the given direction of dest.  If the
    465      *   dest is not in the direction from source, return 0.
    466      */
    467     static int majorAxisDistance(int direction, Rect source, Rect dest) {
    468         return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
    469     }
    470 
    471     static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) {
    472         switch (direction) {
    473             case View.FOCUS_LEFT:
    474                 return source.left - dest.right;
    475             case View.FOCUS_RIGHT:
    476                 return dest.left - source.right;
    477             case View.FOCUS_UP:
    478                 return source.top - dest.bottom;
    479             case View.FOCUS_DOWN:
    480                 return dest.top - source.bottom;
    481         }
    482         throw new IllegalArgumentException("direction must be one of "
    483                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    484     }
    485 
    486     /**
    487      * @return The distance along the major axis w.r.t the direction from the
    488      *   edge of source to the far edge of dest. If the
    489      *   dest is not in the direction from source, return 1 (to break ties with
    490      *   {@link #majorAxisDistance}).
    491      */
    492     static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
    493         return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
    494     }
    495 
    496     static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {
    497         switch (direction) {
    498             case View.FOCUS_LEFT:
    499                 return source.left - dest.left;
    500             case View.FOCUS_RIGHT:
    501                 return dest.right - source.right;
    502             case View.FOCUS_UP:
    503                 return source.top - dest.top;
    504             case View.FOCUS_DOWN:
    505                 return dest.bottom - source.bottom;
    506         }
    507         throw new IllegalArgumentException("direction must be one of "
    508                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    509     }
    510 
    511     /**
    512      * Find the distance on the minor axis w.r.t the direction to the nearest
    513      * edge of the destination rectangle.
    514      * @param direction the direction (up, down, left, right)
    515      * @param source The source rect.
    516      * @param dest The destination rect.
    517      * @return The distance.
    518      */
    519     static int minorAxisDistance(int direction, Rect source, Rect dest) {
    520         switch (direction) {
    521             case View.FOCUS_LEFT:
    522             case View.FOCUS_RIGHT:
    523                 // the distance between the center verticals
    524                 return Math.abs(
    525                         ((source.top + source.height() / 2) -
    526                         ((dest.top + dest.height() / 2))));
    527             case View.FOCUS_UP:
    528             case View.FOCUS_DOWN:
    529                 // the distance between the center horizontals
    530                 return Math.abs(
    531                         ((source.left + source.width() / 2) -
    532                         ((dest.left + dest.width() / 2))));
    533         }
    534         throw new IllegalArgumentException("direction must be one of "
    535                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    536     }
    537 
    538     /**
    539      * Find the nearest touchable view to the specified view.
    540      *
    541      * @param root The root of the tree in which to search
    542      * @param x X coordinate from which to start the search
    543      * @param y Y coordinate from which to start the search
    544      * @param direction Direction to look
    545      * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array
    546      *        may already be populated with values.
    547      * @return The nearest touchable view, or null if none exists.
    548      */
    549     public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) {
    550         ArrayList<View> touchables = root.getTouchables();
    551         int minDistance = Integer.MAX_VALUE;
    552         View closest = null;
    553 
    554         int numTouchables = touchables.size();
    555 
    556         int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
    557 
    558         Rect closestBounds = new Rect();
    559         Rect touchableBounds = mOtherRect;
    560 
    561         for (int i = 0; i < numTouchables; i++) {
    562             View touchable = touchables.get(i);
    563 
    564             // get visible bounds of other view in same coordinate system
    565             touchable.getDrawingRect(touchableBounds);
    566 
    567             root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true);
    568 
    569             if (!isTouchCandidate(x, y, touchableBounds, direction)) {
    570                 continue;
    571             }
    572 
    573             int distance = Integer.MAX_VALUE;
    574 
    575             switch (direction) {
    576             case View.FOCUS_LEFT:
    577                 distance = x - touchableBounds.right + 1;
    578                 break;
    579             case View.FOCUS_RIGHT:
    580                 distance = touchableBounds.left;
    581                 break;
    582             case View.FOCUS_UP:
    583                 distance = y - touchableBounds.bottom + 1;
    584                 break;
    585             case View.FOCUS_DOWN:
    586                 distance = touchableBounds.top;
    587                 break;
    588             }
    589 
    590             if (distance < edgeSlop) {
    591                 // Give preference to innermost views
    592                 if (closest == null ||
    593                         closestBounds.contains(touchableBounds) ||
    594                         (!touchableBounds.contains(closestBounds) && distance < minDistance)) {
    595                     minDistance = distance;
    596                     closest = touchable;
    597                     closestBounds.set(touchableBounds);
    598                     switch (direction) {
    599                     case View.FOCUS_LEFT:
    600                         deltas[0] = -distance;
    601                         break;
    602                     case View.FOCUS_RIGHT:
    603                         deltas[0] = distance;
    604                         break;
    605                     case View.FOCUS_UP:
    606                         deltas[1] = -distance;
    607                         break;
    608                     case View.FOCUS_DOWN:
    609                         deltas[1] = distance;
    610                         break;
    611                     }
    612                 }
    613             }
    614         }
    615         return closest;
    616     }
    617 
    618 
    619     /**
    620      * Is destRect a candidate for the next touch given the direction?
    621      */
    622     private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) {
    623         switch (direction) {
    624             case View.FOCUS_LEFT:
    625                 return destRect.left <= x && destRect.top <= y && y <= destRect.bottom;
    626             case View.FOCUS_RIGHT:
    627                 return destRect.left >= x && destRect.top <= y && y <= destRect.bottom;
    628             case View.FOCUS_UP:
    629                 return destRect.top <= y && destRect.left <= x && x <= destRect.right;
    630             case View.FOCUS_DOWN:
    631                 return destRect.top >= y && destRect.left <= x && x <= destRect.right;
    632         }
    633         throw new IllegalArgumentException("direction must be one of "
    634                 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
    635     }
    636 
    637     /**
    638      * Sorts views according to their visual layout and geometry for default tab order.
    639      * This is used for sequential focus traversal.
    640      */
    641     private static final class SequentialFocusComparator implements Comparator<View> {
    642         private final Rect mFirstRect = new Rect();
    643         private final Rect mSecondRect = new Rect();
    644         private ViewGroup mRoot;
    645 
    646         public void recycle() {
    647             mRoot = null;
    648         }
    649 
    650         public void setRoot(ViewGroup root) {
    651             mRoot = root;
    652         }
    653 
    654         public int compare(View first, View second) {
    655             if (first == second) {
    656                 return 0;
    657             }
    658 
    659             getRect(first, mFirstRect);
    660             getRect(second, mSecondRect);
    661 
    662             if (mFirstRect.top < mSecondRect.top) {
    663                 return -1;
    664             } else if (mFirstRect.top > mSecondRect.top) {
    665                 return 1;
    666             } else if (mFirstRect.left < mSecondRect.left) {
    667                 return -1;
    668             } else if (mFirstRect.left > mSecondRect.left) {
    669                 return 1;
    670             } else if (mFirstRect.bottom < mSecondRect.bottom) {
    671                 return -1;
    672             } else if (mFirstRect.bottom > mSecondRect.bottom) {
    673                 return 1;
    674             } else if (mFirstRect.right < mSecondRect.right) {
    675                 return -1;
    676             } else if (mFirstRect.right > mSecondRect.right) {
    677                 return 1;
    678             } else {
    679                 // The view are distinct but completely coincident so we consider
    680                 // them equal for our purposes.  Since the sort is stable, this
    681                 // means that the views will retain their layout order relative to one another.
    682                 return 0;
    683             }
    684         }
    685 
    686         private void getRect(View view, Rect rect) {
    687             view.getDrawingRect(rect);
    688             mRoot.offsetDescendantRectToMyCoords(view, rect);
    689         }
    690     }
    691 }
    692