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