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