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      */
getInstance()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
FocusFinder()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      */
findNextFocus(ViewGroup root, View focused, int direction)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      */
findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction)76     public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
77         mFocusedRect.set(focusedRect);
78         return findNextFocus(root, null, mFocusedRect, direction);
79     }
80 
findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction)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 
findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction)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 
findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList<View> focusables)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 
findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction)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 
setFocusBottomRight(ViewGroup root, Rect focusedRect)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 
setFocusTopLeft(ViewGroup root, Rect focusedRect)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 
findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction)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 
getNextFocusable(View focused, ArrayList<View> focusables, int count)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 
getPreviousFocusable(View focused, ArrayList<View> focusables, int count)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      */
isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2)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      */
beamBeats(int direction, Rect source, Rect rect1, Rect rect2)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      */
getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance)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      */
isCandidate(Rect srcRect, Rect destRect, int direction)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      */
beamsOverlap(int direction, Rect rect1, Rect rect2)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      */
isToDirectionOf(int direction, Rect src, Rect dest)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      */
majorAxisDistance(int direction, Rect source, Rect dest)431     static int majorAxisDistance(int direction, Rect source, Rect dest) {
432         return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
433     }
434 
majorAxisDistanceRaw(int direction, Rect source, Rect dest)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      */
majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest)456     static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) {
457         return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
458     }
459 
majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest)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      */
minorAxisDistance(int direction, Rect source, Rect dest)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      */
findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas)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      */
isTouchCandidate(int x, int y, Rect destRect, int direction)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 
recycle()611         public void recycle() {
612             mRoot = null;
613         }
614 
setRoot(ViewGroup root)615         public void setRoot(ViewGroup root) {
616             mRoot = root;
617         }
618 
setIsLayoutRtl(boolean b)619         public void setIsLayoutRtl(boolean b) {
620             mIsLayoutRtl = b;
621         }
622 
compare(View first, View second)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 
getRect(View view, Rect rect)655         private void getRect(View view, Rect rect) {
656             view.getDrawingRect(rect);
657             mRoot.offsetDescendantRectToMyCoords(view, rect);
658         }
659     }
660 }
661