1 /* 2 * Copyright (C) 2008 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 import android.graphics.Region; 21 22 import java.util.ArrayList; 23 import java.util.concurrent.CopyOnWriteArrayList; 24 25 /** 26 * A view tree observer is used to register listeners that can be notified of global 27 * changes in the view tree. Such global events include, but are not limited to, 28 * layout of the whole tree, beginning of the drawing pass, touch mode change.... 29 * 30 * A ViewTreeObserver should never be instantiated by applications as it is provided 31 * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()} 32 * for more information. 33 */ 34 public final class ViewTreeObserver { 35 // Recursive listeners use CopyOnWriteArrayList 36 private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners; 37 private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners; 38 private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; 39 private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; 40 41 // Non-recursive listeners use CopyOnWriteArray 42 // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive 43 private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; 44 private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; 45 private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; 46 private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; 47 48 // These listeners cannot be mutated during dispatch 49 private ArrayList<OnDrawListener> mOnDrawListeners; 50 51 private boolean mAlive = true; 52 53 /** 54 * Interface definition for a callback to be invoked when the view hierarchy is 55 * attached to and detached from its window. 56 */ 57 public interface OnWindowAttachListener { 58 /** 59 * Callback method to be invoked when the view hierarchy is attached to a window 60 */ 61 public void onWindowAttached(); 62 63 /** 64 * Callback method to be invoked when the view hierarchy is detached from a window 65 */ 66 public void onWindowDetached(); 67 } 68 69 /** 70 * Interface definition for a callback to be invoked when the view hierarchy's window 71 * focus state changes. 72 */ 73 public interface OnWindowFocusChangeListener { 74 /** 75 * Callback method to be invoked when the window focus changes in the view tree. 76 * 77 * @param hasFocus Set to true if the window is gaining focus, false if it is 78 * losing focus. 79 */ 80 public void onWindowFocusChanged(boolean hasFocus); 81 } 82 83 /** 84 * Interface definition for a callback to be invoked when the focus state within 85 * the view tree changes. 86 */ 87 public interface OnGlobalFocusChangeListener { 88 /** 89 * Callback method to be invoked when the focus changes in the view tree. When 90 * the view tree transitions from touch mode to non-touch mode, oldFocus is null. 91 * When the view tree transitions from non-touch mode to touch mode, newFocus is 92 * null. When focus changes in non-touch mode (without transition from or to 93 * touch mode) either oldFocus or newFocus can be null. 94 * 95 * @param oldFocus The previously focused view, if any. 96 * @param newFocus The newly focused View, if any. 97 */ 98 public void onGlobalFocusChanged(View oldFocus, View newFocus); 99 } 100 101 /** 102 * Interface definition for a callback to be invoked when the global layout state 103 * or the visibility of views within the view tree changes. 104 */ 105 public interface OnGlobalLayoutListener { 106 /** 107 * Callback method to be invoked when the global layout state or the visibility of views 108 * within the view tree changes 109 */ 110 public void onGlobalLayout(); 111 } 112 113 /** 114 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 115 */ 116 public interface OnPreDrawListener { 117 /** 118 * Callback method to be invoked when the view tree is about to be drawn. At this point, all 119 * views in the tree have been measured and given a frame. Clients can use this to adjust 120 * their scroll bounds or even to request a new layout before drawing occurs. 121 * 122 * @return Return true to proceed with the current drawing pass, or false to cancel. 123 * 124 * @see android.view.View#onMeasure 125 * @see android.view.View#onLayout 126 * @see android.view.View#onDraw 127 */ 128 public boolean onPreDraw(); 129 } 130 131 /** 132 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 133 */ 134 public interface OnDrawListener { 135 /** 136 * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, 137 * views cannot be modified in any way.</p> 138 * 139 * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the 140 * current drawing pass.</p> 141 * 142 * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> 143 * from this method.</p> 144 * 145 * @see android.view.View#onMeasure 146 * @see android.view.View#onLayout 147 * @see android.view.View#onDraw 148 */ 149 public void onDraw(); 150 } 151 152 /** 153 * Interface definition for a callback to be invoked when the touch mode changes. 154 */ 155 public interface OnTouchModeChangeListener { 156 /** 157 * Callback method to be invoked when the touch mode changes. 158 * 159 * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise. 160 */ 161 public void onTouchModeChanged(boolean isInTouchMode); 162 } 163 164 /** 165 * Interface definition for a callback to be invoked when 166 * something in the view tree has been scrolled. 167 */ 168 public interface OnScrollChangedListener { 169 /** 170 * Callback method to be invoked when something in the view tree 171 * has been scrolled. 172 */ 173 public void onScrollChanged(); 174 } 175 176 /** 177 * Parameters used with OnComputeInternalInsetsListener. 178 * 179 * We are not yet ready to commit to this API and support it, so 180 * @hide 181 */ 182 public final static class InternalInsetsInfo { 183 /** 184 * Offsets from the frame of the window at which the content of 185 * windows behind it should be placed. 186 */ 187 public final Rect contentInsets = new Rect(); 188 189 /** 190 * Offsets from the frame of the window at which windows behind it 191 * are visible. 192 */ 193 public final Rect visibleInsets = new Rect(); 194 195 /** 196 * Touchable region defined relative to the origin of the frame of the window. 197 * Only used when {@link #setTouchableInsets(int)} is called with 198 * the option {@link #TOUCHABLE_INSETS_REGION}. 199 */ 200 public final Region touchableRegion = new Region(); 201 202 /** 203 * Option for {@link #setTouchableInsets(int)}: the entire window frame 204 * can be touched. 205 */ 206 public static final int TOUCHABLE_INSETS_FRAME = 0; 207 208 /** 209 * Option for {@link #setTouchableInsets(int)}: the area inside of 210 * the content insets can be touched. 211 */ 212 public static final int TOUCHABLE_INSETS_CONTENT = 1; 213 214 /** 215 * Option for {@link #setTouchableInsets(int)}: the area inside of 216 * the visible insets can be touched. 217 */ 218 public static final int TOUCHABLE_INSETS_VISIBLE = 2; 219 220 /** 221 * Option for {@link #setTouchableInsets(int)}: the area inside of 222 * the provided touchable region in {@link #touchableRegion} can be touched. 223 */ 224 public static final int TOUCHABLE_INSETS_REGION = 3; 225 226 /** 227 * Set which parts of the window can be touched: either 228 * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT}, 229 * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}. 230 */ 231 public void setTouchableInsets(int val) { 232 mTouchableInsets = val; 233 } 234 235 int mTouchableInsets; 236 237 void reset() { 238 contentInsets.setEmpty(); 239 visibleInsets.setEmpty(); 240 touchableRegion.setEmpty(); 241 mTouchableInsets = TOUCHABLE_INSETS_FRAME; 242 } 243 244 @Override 245 public int hashCode() { 246 int result = contentInsets != null ? contentInsets.hashCode() : 0; 247 result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); 248 result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); 249 result = 31 * result + mTouchableInsets; 250 return result; 251 } 252 253 @Override 254 public boolean equals(Object o) { 255 if (this == o) return true; 256 if (o == null || getClass() != o.getClass()) return false; 257 258 InternalInsetsInfo other = (InternalInsetsInfo)o; 259 return mTouchableInsets == other.mTouchableInsets && 260 contentInsets.equals(other.contentInsets) && 261 visibleInsets.equals(other.visibleInsets) && 262 touchableRegion.equals(other.touchableRegion); 263 } 264 265 void set(InternalInsetsInfo other) { 266 contentInsets.set(other.contentInsets); 267 visibleInsets.set(other.visibleInsets); 268 touchableRegion.set(other.touchableRegion); 269 mTouchableInsets = other.mTouchableInsets; 270 } 271 } 272 273 /** 274 * Interface definition for a callback to be invoked when layout has 275 * completed and the client can compute its interior insets. 276 * 277 * We are not yet ready to commit to this API and support it, so 278 * @hide 279 */ 280 public interface OnComputeInternalInsetsListener { 281 /** 282 * Callback method to be invoked when layout has completed and the 283 * client can compute its interior insets. 284 * 285 * @param inoutInfo Should be filled in by the implementation with 286 * the information about the insets of the window. This is called 287 * with whatever values the previous OnComputeInternalInsetsListener 288 * returned, if there are multiple such listeners in the window. 289 */ 290 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo); 291 } 292 293 /** 294 * Creates a new ViewTreeObserver. This constructor should not be called 295 */ 296 ViewTreeObserver() { 297 } 298 299 /** 300 * Merges all the listeners registered on the specified observer with the listeners 301 * registered on this object. After this method is invoked, the specified observer 302 * will return false in {@link #isAlive()} and should not be used anymore. 303 * 304 * @param observer The ViewTreeObserver whose listeners must be added to this observer 305 */ 306 void merge(ViewTreeObserver observer) { 307 if (observer.mOnWindowAttachListeners != null) { 308 if (mOnWindowAttachListeners != null) { 309 mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners); 310 } else { 311 mOnWindowAttachListeners = observer.mOnWindowAttachListeners; 312 } 313 } 314 315 if (observer.mOnWindowFocusListeners != null) { 316 if (mOnWindowFocusListeners != null) { 317 mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners); 318 } else { 319 mOnWindowFocusListeners = observer.mOnWindowFocusListeners; 320 } 321 } 322 323 if (observer.mOnGlobalFocusListeners != null) { 324 if (mOnGlobalFocusListeners != null) { 325 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners); 326 } else { 327 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners; 328 } 329 } 330 331 if (observer.mOnGlobalLayoutListeners != null) { 332 if (mOnGlobalLayoutListeners != null) { 333 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); 334 } else { 335 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners; 336 } 337 } 338 339 if (observer.mOnPreDrawListeners != null) { 340 if (mOnPreDrawListeners != null) { 341 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners); 342 } else { 343 mOnPreDrawListeners = observer.mOnPreDrawListeners; 344 } 345 } 346 347 if (observer.mOnTouchModeChangeListeners != null) { 348 if (mOnTouchModeChangeListeners != null) { 349 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); 350 } else { 351 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners; 352 } 353 } 354 355 if (observer.mOnComputeInternalInsetsListeners != null) { 356 if (mOnComputeInternalInsetsListeners != null) { 357 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners); 358 } else { 359 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners; 360 } 361 } 362 363 if (observer.mOnScrollChangedListeners != null) { 364 if (mOnScrollChangedListeners != null) { 365 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners); 366 } else { 367 mOnScrollChangedListeners = observer.mOnScrollChangedListeners; 368 } 369 } 370 371 observer.kill(); 372 } 373 374 /** 375 * Register a callback to be invoked when the view hierarchy is attached to a window. 376 * 377 * @param listener The callback to add 378 * 379 * @throws IllegalStateException If {@link #isAlive()} returns false 380 */ 381 public void addOnWindowAttachListener(OnWindowAttachListener listener) { 382 checkIsAlive(); 383 384 if (mOnWindowAttachListeners == null) { 385 mOnWindowAttachListeners 386 = new CopyOnWriteArrayList<OnWindowAttachListener>(); 387 } 388 389 mOnWindowAttachListeners.add(listener); 390 } 391 392 /** 393 * Remove a previously installed window attach callback. 394 * 395 * @param victim The callback to remove 396 * 397 * @throws IllegalStateException If {@link #isAlive()} returns false 398 * 399 * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener) 400 */ 401 public void removeOnWindowAttachListener(OnWindowAttachListener victim) { 402 checkIsAlive(); 403 if (mOnWindowAttachListeners == null) { 404 return; 405 } 406 mOnWindowAttachListeners.remove(victim); 407 } 408 409 /** 410 * Register a callback to be invoked when the window focus state within the view tree changes. 411 * 412 * @param listener The callback to add 413 * 414 * @throws IllegalStateException If {@link #isAlive()} returns false 415 */ 416 public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) { 417 checkIsAlive(); 418 419 if (mOnWindowFocusListeners == null) { 420 mOnWindowFocusListeners 421 = new CopyOnWriteArrayList<OnWindowFocusChangeListener>(); 422 } 423 424 mOnWindowFocusListeners.add(listener); 425 } 426 427 /** 428 * Remove a previously installed window focus change callback. 429 * 430 * @param victim The callback to remove 431 * 432 * @throws IllegalStateException If {@link #isAlive()} returns false 433 * 434 * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener) 435 */ 436 public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) { 437 checkIsAlive(); 438 if (mOnWindowFocusListeners == null) { 439 return; 440 } 441 mOnWindowFocusListeners.remove(victim); 442 } 443 444 /** 445 * Register a callback to be invoked when the focus state within the view tree changes. 446 * 447 * @param listener The callback to add 448 * 449 * @throws IllegalStateException If {@link #isAlive()} returns false 450 */ 451 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) { 452 checkIsAlive(); 453 454 if (mOnGlobalFocusListeners == null) { 455 mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); 456 } 457 458 mOnGlobalFocusListeners.add(listener); 459 } 460 461 /** 462 * Remove a previously installed focus change callback. 463 * 464 * @param victim The callback to remove 465 * 466 * @throws IllegalStateException If {@link #isAlive()} returns false 467 * 468 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener) 469 */ 470 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) { 471 checkIsAlive(); 472 if (mOnGlobalFocusListeners == null) { 473 return; 474 } 475 mOnGlobalFocusListeners.remove(victim); 476 } 477 478 /** 479 * Register a callback to be invoked when the global layout state or the visibility of views 480 * within the view tree changes 481 * 482 * @param listener The callback to add 483 * 484 * @throws IllegalStateException If {@link #isAlive()} returns false 485 */ 486 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) { 487 checkIsAlive(); 488 489 if (mOnGlobalLayoutListeners == null) { 490 mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>(); 491 } 492 493 mOnGlobalLayoutListeners.add(listener); 494 } 495 496 /** 497 * Remove a previously installed global layout callback 498 * 499 * @param victim The callback to remove 500 * 501 * @throws IllegalStateException If {@link #isAlive()} returns false 502 * 503 * @deprecated Use #removeOnGlobalLayoutListener instead 504 * 505 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 506 */ 507 @Deprecated 508 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { 509 removeOnGlobalLayoutListener(victim); 510 } 511 512 /** 513 * Remove a previously installed global layout callback 514 * 515 * @param victim The callback to remove 516 * 517 * @throws IllegalStateException If {@link #isAlive()} returns false 518 * 519 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 520 */ 521 public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) { 522 checkIsAlive(); 523 if (mOnGlobalLayoutListeners == null) { 524 return; 525 } 526 mOnGlobalLayoutListeners.remove(victim); 527 } 528 529 /** 530 * Register a callback to be invoked when the view tree is about to be drawn 531 * 532 * @param listener The callback to add 533 * 534 * @throws IllegalStateException If {@link #isAlive()} returns false 535 */ 536 public void addOnPreDrawListener(OnPreDrawListener listener) { 537 checkIsAlive(); 538 539 if (mOnPreDrawListeners == null) { 540 mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); 541 } 542 543 mOnPreDrawListeners.add(listener); 544 } 545 546 /** 547 * Remove a previously installed pre-draw callback 548 * 549 * @param victim The callback to remove 550 * 551 * @throws IllegalStateException If {@link #isAlive()} returns false 552 * 553 * @see #addOnPreDrawListener(OnPreDrawListener) 554 */ 555 public void removeOnPreDrawListener(OnPreDrawListener victim) { 556 checkIsAlive(); 557 if (mOnPreDrawListeners == null) { 558 return; 559 } 560 mOnPreDrawListeners.remove(victim); 561 } 562 563 /** 564 * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> 565 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 566 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 567 * 568 * @param listener The callback to add 569 * 570 * @throws IllegalStateException If {@link #isAlive()} returns false 571 */ 572 public void addOnDrawListener(OnDrawListener listener) { 573 checkIsAlive(); 574 575 if (mOnDrawListeners == null) { 576 mOnDrawListeners = new ArrayList<OnDrawListener>(); 577 } 578 579 mOnDrawListeners.add(listener); 580 } 581 582 /** 583 * <p>Remove a previously installed pre-draw callback.</p> 584 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 585 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 586 * 587 * @param victim The callback to remove 588 * 589 * @throws IllegalStateException If {@link #isAlive()} returns false 590 * 591 * @see #addOnDrawListener(OnDrawListener) 592 */ 593 public void removeOnDrawListener(OnDrawListener victim) { 594 checkIsAlive(); 595 if (mOnDrawListeners == null) { 596 return; 597 } 598 mOnDrawListeners.remove(victim); 599 } 600 601 /** 602 * Register a callback to be invoked when a view has been scrolled. 603 * 604 * @param listener The callback to add 605 * 606 * @throws IllegalStateException If {@link #isAlive()} returns false 607 */ 608 public void addOnScrollChangedListener(OnScrollChangedListener listener) { 609 checkIsAlive(); 610 611 if (mOnScrollChangedListeners == null) { 612 mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>(); 613 } 614 615 mOnScrollChangedListeners.add(listener); 616 } 617 618 /** 619 * Remove a previously installed scroll-changed callback 620 * 621 * @param victim The callback to remove 622 * 623 * @throws IllegalStateException If {@link #isAlive()} returns false 624 * 625 * @see #addOnScrollChangedListener(OnScrollChangedListener) 626 */ 627 public void removeOnScrollChangedListener(OnScrollChangedListener victim) { 628 checkIsAlive(); 629 if (mOnScrollChangedListeners == null) { 630 return; 631 } 632 mOnScrollChangedListeners.remove(victim); 633 } 634 635 /** 636 * Register a callback to be invoked when the invoked when the touch mode changes. 637 * 638 * @param listener The callback to add 639 * 640 * @throws IllegalStateException If {@link #isAlive()} returns false 641 */ 642 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) { 643 checkIsAlive(); 644 645 if (mOnTouchModeChangeListeners == null) { 646 mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); 647 } 648 649 mOnTouchModeChangeListeners.add(listener); 650 } 651 652 /** 653 * Remove a previously installed touch mode change callback 654 * 655 * @param victim The callback to remove 656 * 657 * @throws IllegalStateException If {@link #isAlive()} returns false 658 * 659 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener) 660 */ 661 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) { 662 checkIsAlive(); 663 if (mOnTouchModeChangeListeners == null) { 664 return; 665 } 666 mOnTouchModeChangeListeners.remove(victim); 667 } 668 669 /** 670 * Register a callback to be invoked when the invoked when it is time to 671 * compute the window's internal insets. 672 * 673 * @param listener The callback to add 674 * 675 * @throws IllegalStateException If {@link #isAlive()} returns false 676 * 677 * We are not yet ready to commit to this API and support it, so 678 * @hide 679 */ 680 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) { 681 checkIsAlive(); 682 683 if (mOnComputeInternalInsetsListeners == null) { 684 mOnComputeInternalInsetsListeners = 685 new CopyOnWriteArray<OnComputeInternalInsetsListener>(); 686 } 687 688 mOnComputeInternalInsetsListeners.add(listener); 689 } 690 691 /** 692 * Remove a previously installed internal insets computation callback 693 * 694 * @param victim The callback to remove 695 * 696 * @throws IllegalStateException If {@link #isAlive()} returns false 697 * 698 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener) 699 * 700 * We are not yet ready to commit to this API and support it, so 701 * @hide 702 */ 703 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) { 704 checkIsAlive(); 705 if (mOnComputeInternalInsetsListeners == null) { 706 return; 707 } 708 mOnComputeInternalInsetsListeners.remove(victim); 709 } 710 711 private void checkIsAlive() { 712 if (!mAlive) { 713 throw new IllegalStateException("This ViewTreeObserver is not alive, call " 714 + "getViewTreeObserver() again"); 715 } 716 } 717 718 /** 719 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive, 720 * any call to a method (except this one) will throw an exception. 721 * 722 * If an application keeps a long-lived reference to this ViewTreeObserver, it should 723 * always check for the result of this method before calling any other method. 724 * 725 * @return True if this object is alive and be used, false otherwise. 726 */ 727 public boolean isAlive() { 728 return mAlive; 729 } 730 731 /** 732 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking 733 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception. 734 * 735 * @hide 736 */ 737 private void kill() { 738 mAlive = false; 739 } 740 741 /** 742 * Notifies registered listeners that window has been attached/detached. 743 */ 744 final void dispatchOnWindowAttachedChange(boolean attached) { 745 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 746 // perform the dispatching. The iterator is a safe guard against listeners that 747 // could mutate the list by calling the various add/remove methods. This prevents 748 // the array from being modified while we iterate it. 749 final CopyOnWriteArrayList<OnWindowAttachListener> listeners 750 = mOnWindowAttachListeners; 751 if (listeners != null && listeners.size() > 0) { 752 for (OnWindowAttachListener listener : listeners) { 753 if (attached) listener.onWindowAttached(); 754 else listener.onWindowDetached(); 755 } 756 } 757 } 758 759 /** 760 * Notifies registered listeners that window focus has changed. 761 */ 762 final void dispatchOnWindowFocusChange(boolean hasFocus) { 763 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 764 // perform the dispatching. The iterator is a safe guard against listeners that 765 // could mutate the list by calling the various add/remove methods. This prevents 766 // the array from being modified while we iterate it. 767 final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners 768 = mOnWindowFocusListeners; 769 if (listeners != null && listeners.size() > 0) { 770 for (OnWindowFocusChangeListener listener : listeners) { 771 listener.onWindowFocusChanged(hasFocus); 772 } 773 } 774 } 775 776 /** 777 * Notifies registered listeners that focus has changed. 778 */ 779 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { 780 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 781 // perform the dispatching. The iterator is a safe guard against listeners that 782 // could mutate the list by calling the various add/remove methods. This prevents 783 // the array from being modified while we iterate it. 784 final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; 785 if (listeners != null && listeners.size() > 0) { 786 for (OnGlobalFocusChangeListener listener : listeners) { 787 listener.onGlobalFocusChanged(oldFocus, newFocus); 788 } 789 } 790 } 791 792 /** 793 * Notifies registered listeners that a global layout happened. This can be called 794 * manually if you are forcing a layout on a View or a hierarchy of Views that are 795 * not attached to a Window or in the GONE state. 796 */ 797 public final void dispatchOnGlobalLayout() { 798 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 799 // perform the dispatching. The iterator is a safe guard against listeners that 800 // could mutate the list by calling the various add/remove methods. This prevents 801 // the array from being modified while we iterate it. 802 final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; 803 if (listeners != null && listeners.size() > 0) { 804 CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); 805 try { 806 int count = access.size(); 807 for (int i = 0; i < count; i++) { 808 access.get(i).onGlobalLayout(); 809 } 810 } finally { 811 listeners.end(); 812 } 813 } 814 } 815 816 /** 817 * Notifies registered listeners that the drawing pass is about to start. If a 818 * listener returns true, then the drawing pass is canceled and rescheduled. This can 819 * be called manually if you are forcing the drawing on a View or a hierarchy of Views 820 * that are not attached to a Window or in the GONE state. 821 * 822 * @return True if the current draw should be canceled and resceduled, false otherwise. 823 */ 824 @SuppressWarnings("unchecked") 825 public final boolean dispatchOnPreDraw() { 826 boolean cancelDraw = false; 827 final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; 828 if (listeners != null && listeners.size() > 0) { 829 CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); 830 try { 831 int count = access.size(); 832 for (int i = 0; i < count; i++) { 833 cancelDraw |= !(access.get(i).onPreDraw()); 834 } 835 } finally { 836 listeners.end(); 837 } 838 } 839 return cancelDraw; 840 } 841 842 /** 843 * Notifies registered listeners that the drawing pass is about to start. 844 */ 845 public final void dispatchOnDraw() { 846 if (mOnDrawListeners != null) { 847 final ArrayList<OnDrawListener> listeners = mOnDrawListeners; 848 int numListeners = listeners.size(); 849 for (int i = 0; i < numListeners; ++i) { 850 listeners.get(i).onDraw(); 851 } 852 } 853 } 854 855 /** 856 * Notifies registered listeners that the touch mode has changed. 857 * 858 * @param inTouchMode True if the touch mode is now enabled, false otherwise. 859 */ 860 final void dispatchOnTouchModeChanged(boolean inTouchMode) { 861 final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = 862 mOnTouchModeChangeListeners; 863 if (listeners != null && listeners.size() > 0) { 864 for (OnTouchModeChangeListener listener : listeners) { 865 listener.onTouchModeChanged(inTouchMode); 866 } 867 } 868 } 869 870 /** 871 * Notifies registered listeners that something has scrolled. 872 */ 873 final void dispatchOnScrollChanged() { 874 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 875 // perform the dispatching. The iterator is a safe guard against listeners that 876 // could mutate the list by calling the various add/remove methods. This prevents 877 // the array from being modified while we iterate it. 878 final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners; 879 if (listeners != null && listeners.size() > 0) { 880 CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start(); 881 try { 882 int count = access.size(); 883 for (int i = 0; i < count; i++) { 884 access.get(i).onScrollChanged(); 885 } 886 } finally { 887 listeners.end(); 888 } 889 } 890 } 891 892 /** 893 * Returns whether there are listeners for computing internal insets. 894 */ 895 final boolean hasComputeInternalInsetsListeners() { 896 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = 897 mOnComputeInternalInsetsListeners; 898 return (listeners != null && listeners.size() > 0); 899 } 900 901 /** 902 * Calls all listeners to compute the current insets. 903 */ 904 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) { 905 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 906 // perform the dispatching. The iterator is a safe guard against listeners that 907 // could mutate the list by calling the various add/remove methods. This prevents 908 // the array from being modified while we iterate it. 909 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = 910 mOnComputeInternalInsetsListeners; 911 if (listeners != null && listeners.size() > 0) { 912 CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start(); 913 try { 914 int count = access.size(); 915 for (int i = 0; i < count; i++) { 916 access.get(i).onComputeInternalInsets(inoutInfo); 917 } 918 } finally { 919 listeners.end(); 920 } 921 } 922 } 923 924 /** 925 * Copy on write array. This array is not thread safe, and only one loop can 926 * iterate over this array at any given time. This class avoids allocations 927 * until a concurrent modification happens. 928 * 929 * Usage: 930 * 931 * CopyOnWriteArray.Access<MyData> access = array.start(); 932 * try { 933 * for (int i = 0; i < access.size(); i++) { 934 * MyData d = access.get(i); 935 * } 936 * } finally { 937 * access.end(); 938 * } 939 */ 940 static class CopyOnWriteArray<T> { 941 private ArrayList<T> mData = new ArrayList<T>(); 942 private ArrayList<T> mDataCopy; 943 944 private final Access<T> mAccess = new Access<T>(); 945 946 private boolean mStart; 947 948 static class Access<T> { 949 private ArrayList<T> mData; 950 private int mSize; 951 952 T get(int index) { 953 return mData.get(index); 954 } 955 956 int size() { 957 return mSize; 958 } 959 } 960 961 CopyOnWriteArray() { 962 } 963 964 private ArrayList<T> getArray() { 965 if (mStart) { 966 if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData); 967 return mDataCopy; 968 } 969 return mData; 970 } 971 972 Access<T> start() { 973 if (mStart) throw new IllegalStateException("Iteration already started"); 974 mStart = true; 975 mDataCopy = null; 976 mAccess.mData = mData; 977 mAccess.mSize = mData.size(); 978 return mAccess; 979 } 980 981 void end() { 982 if (!mStart) throw new IllegalStateException("Iteration not started"); 983 mStart = false; 984 if (mDataCopy != null) { 985 mData = mDataCopy; 986 } 987 mDataCopy = null; 988 } 989 990 int size() { 991 return getArray().size(); 992 } 993 994 void add(T item) { 995 getArray().add(item); 996 } 997 998 void addAll(CopyOnWriteArray<T> array) { 999 getArray().addAll(array.mData); 1000 } 1001 1002 void remove(T item) { 1003 getArray().remove(item); 1004 } 1005 1006 void clear() { 1007 getArray().clear(); 1008 } 1009 } 1010 } 1011