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