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