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