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