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 private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; 36 private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; 37 private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; 38 private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; 39 private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; 40 private ArrayList<OnPreDrawListener> mOnPreDrawListeners; 41 private ArrayList<OnDrawListener> mOnDrawListeners; 42 43 private boolean mAlive = true; 44 45 /** 46 * Interface definition for a callback to be invoked when the focus state within 47 * the view tree changes. 48 */ 49 public interface OnGlobalFocusChangeListener { 50 /** 51 * Callback method to be invoked when the focus changes in the view tree. When 52 * the view tree transitions from touch mode to non-touch mode, oldFocus is null. 53 * When the view tree transitions from non-touch mode to touch mode, newFocus is 54 * null. When focus changes in non-touch mode (without transition from or to 55 * touch mode) either oldFocus or newFocus can be null. 56 * 57 * @param oldFocus The previously focused view, if any. 58 * @param newFocus The newly focused View, if any. 59 */ onGlobalFocusChanged(View oldFocus, View newFocus)60 public void onGlobalFocusChanged(View oldFocus, View newFocus); 61 } 62 63 /** 64 * Interface definition for a callback to be invoked when the global layout state 65 * or the visibility of views within the view tree changes. 66 */ 67 public interface OnGlobalLayoutListener { 68 /** 69 * Callback method to be invoked when the global layout state or the visibility of views 70 * within the view tree changes 71 */ onGlobalLayout()72 public void onGlobalLayout(); 73 } 74 75 /** 76 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 77 */ 78 public interface OnPreDrawListener { 79 /** 80 * Callback method to be invoked when the view tree is about to be drawn. At this point, all 81 * views in the tree have been measured and given a frame. Clients can use this to adjust 82 * their scroll bounds or even to request a new layout before drawing occurs. 83 * 84 * @return Return true to proceed with the current drawing pass, or false to cancel. 85 * 86 * @see android.view.View#onMeasure 87 * @see android.view.View#onLayout 88 * @see android.view.View#onDraw 89 */ onPreDraw()90 public boolean onPreDraw(); 91 } 92 93 /** 94 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 95 */ 96 public interface OnDrawListener { 97 /** 98 * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, 99 * views cannot be modified in any way.</p> 100 * 101 * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the 102 * current drawing pass.</p> 103 * 104 * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> 105 * from this method.</p> 106 * 107 * @see android.view.View#onMeasure 108 * @see android.view.View#onLayout 109 * @see android.view.View#onDraw 110 */ onDraw()111 public void onDraw(); 112 } 113 114 /** 115 * Interface definition for a callback to be invoked when the touch mode changes. 116 */ 117 public interface OnTouchModeChangeListener { 118 /** 119 * Callback method to be invoked when the touch mode changes. 120 * 121 * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise. 122 */ onTouchModeChanged(boolean isInTouchMode)123 public void onTouchModeChanged(boolean isInTouchMode); 124 } 125 126 /** 127 * Interface definition for a callback to be invoked when 128 * something in the view tree has been scrolled. 129 */ 130 public interface OnScrollChangedListener { 131 /** 132 * Callback method to be invoked when something in the view tree 133 * has been scrolled. 134 */ onScrollChanged()135 public void onScrollChanged(); 136 } 137 138 /** 139 * Parameters used with OnComputeInternalInsetsListener. 140 * 141 * We are not yet ready to commit to this API and support it, so 142 * @hide 143 */ 144 public final static class InternalInsetsInfo { 145 /** 146 * Offsets from the frame of the window at which the content of 147 * windows behind it should be placed. 148 */ 149 public final Rect contentInsets = new Rect(); 150 151 /** 152 * Offsets from the frame of the window at which windows behind it 153 * are visible. 154 */ 155 public final Rect visibleInsets = new Rect(); 156 157 /** 158 * Touchable region defined relative to the origin of the frame of the window. 159 * Only used when {@link #setTouchableInsets(int)} is called with 160 * the option {@link #TOUCHABLE_INSETS_REGION}. 161 */ 162 public final Region touchableRegion = new Region(); 163 164 /** 165 * Option for {@link #setTouchableInsets(int)}: the entire window frame 166 * can be touched. 167 */ 168 public static final int TOUCHABLE_INSETS_FRAME = 0; 169 170 /** 171 * Option for {@link #setTouchableInsets(int)}: the area inside of 172 * the content insets can be touched. 173 */ 174 public static final int TOUCHABLE_INSETS_CONTENT = 1; 175 176 /** 177 * Option for {@link #setTouchableInsets(int)}: the area inside of 178 * the visible insets can be touched. 179 */ 180 public static final int TOUCHABLE_INSETS_VISIBLE = 2; 181 182 /** 183 * Option for {@link #setTouchableInsets(int)}: the area inside of 184 * the provided touchable region in {@link #touchableRegion} can be touched. 185 */ 186 public static final int TOUCHABLE_INSETS_REGION = 3; 187 188 /** 189 * Set which parts of the window can be touched: either 190 * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT}, 191 * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}. 192 */ setTouchableInsets(int val)193 public void setTouchableInsets(int val) { 194 mTouchableInsets = val; 195 } 196 197 int mTouchableInsets; 198 reset()199 void reset() { 200 contentInsets.setEmpty(); 201 visibleInsets.setEmpty(); 202 touchableRegion.setEmpty(); 203 mTouchableInsets = TOUCHABLE_INSETS_FRAME; 204 } 205 206 @Override hashCode()207 public int hashCode() { 208 int result = contentInsets != null ? contentInsets.hashCode() : 0; 209 result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); 210 result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); 211 result = 31 * result + mTouchableInsets; 212 return result; 213 } 214 215 @Override equals(Object o)216 public boolean equals(Object o) { 217 if (this == o) return true; 218 if (o == null || getClass() != o.getClass()) return false; 219 220 InternalInsetsInfo other = (InternalInsetsInfo)o; 221 return mTouchableInsets == other.mTouchableInsets && 222 contentInsets.equals(other.contentInsets) && 223 visibleInsets.equals(other.visibleInsets) && 224 touchableRegion.equals(other.touchableRegion); 225 } 226 set(InternalInsetsInfo other)227 void set(InternalInsetsInfo other) { 228 contentInsets.set(other.contentInsets); 229 visibleInsets.set(other.visibleInsets); 230 touchableRegion.set(other.touchableRegion); 231 mTouchableInsets = other.mTouchableInsets; 232 } 233 } 234 235 /** 236 * Interface definition for a callback to be invoked when layout has 237 * completed and the client can compute its interior insets. 238 * 239 * We are not yet ready to commit to this API and support it, so 240 * @hide 241 */ 242 public interface OnComputeInternalInsetsListener { 243 /** 244 * Callback method to be invoked when layout has completed and the 245 * client can compute its interior insets. 246 * 247 * @param inoutInfo Should be filled in by the implementation with 248 * the information about the insets of the window. This is called 249 * with whatever values the previous OnComputeInternalInsetsListener 250 * returned, if there are multiple such listeners in the window. 251 */ onComputeInternalInsets(InternalInsetsInfo inoutInfo)252 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo); 253 } 254 255 /** 256 * Creates a new ViewTreeObserver. This constructor should not be called 257 */ ViewTreeObserver()258 ViewTreeObserver() { 259 } 260 261 /** 262 * Merges all the listeners registered on the specified observer with the listeners 263 * registered on this object. After this method is invoked, the specified observer 264 * will return false in {@link #isAlive()} and should not be used anymore. 265 * 266 * @param observer The ViewTreeObserver whose listeners must be added to this observer 267 */ merge(ViewTreeObserver observer)268 void merge(ViewTreeObserver observer) { 269 if (observer.mOnGlobalFocusListeners != null) { 270 if (mOnGlobalFocusListeners != null) { 271 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners); 272 } else { 273 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners; 274 } 275 } 276 277 if (observer.mOnGlobalLayoutListeners != null) { 278 if (mOnGlobalLayoutListeners != null) { 279 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); 280 } else { 281 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners; 282 } 283 } 284 285 if (observer.mOnPreDrawListeners != null) { 286 if (mOnPreDrawListeners != null) { 287 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners); 288 } else { 289 mOnPreDrawListeners = observer.mOnPreDrawListeners; 290 } 291 } 292 293 if (observer.mOnTouchModeChangeListeners != null) { 294 if (mOnTouchModeChangeListeners != null) { 295 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); 296 } else { 297 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners; 298 } 299 } 300 301 if (observer.mOnComputeInternalInsetsListeners != null) { 302 if (mOnComputeInternalInsetsListeners != null) { 303 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners); 304 } else { 305 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners; 306 } 307 } 308 309 if (observer.mOnScrollChangedListeners != null) { 310 if (mOnScrollChangedListeners != null) { 311 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners); 312 } else { 313 mOnScrollChangedListeners = observer.mOnScrollChangedListeners; 314 } 315 } 316 317 observer.kill(); 318 } 319 320 /** 321 * Register a callback to be invoked when the focus state within the view tree changes. 322 * 323 * @param listener The callback to add 324 * 325 * @throws IllegalStateException If {@link #isAlive()} returns false 326 */ addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener)327 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) { 328 checkIsAlive(); 329 330 if (mOnGlobalFocusListeners == null) { 331 mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); 332 } 333 334 mOnGlobalFocusListeners.add(listener); 335 } 336 337 /** 338 * Remove a previously installed focus change callback. 339 * 340 * @param victim The callback to remove 341 * 342 * @throws IllegalStateException If {@link #isAlive()} returns false 343 * 344 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener) 345 */ removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim)346 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) { 347 checkIsAlive(); 348 if (mOnGlobalFocusListeners == null) { 349 return; 350 } 351 mOnGlobalFocusListeners.remove(victim); 352 } 353 354 /** 355 * Register a callback to be invoked when the global layout state or the visibility of views 356 * within the view tree changes 357 * 358 * @param listener The callback to add 359 * 360 * @throws IllegalStateException If {@link #isAlive()} returns false 361 */ addOnGlobalLayoutListener(OnGlobalLayoutListener listener)362 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) { 363 checkIsAlive(); 364 365 if (mOnGlobalLayoutListeners == null) { 366 mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>(); 367 } 368 369 mOnGlobalLayoutListeners.add(listener); 370 } 371 372 /** 373 * Remove a previously installed global layout callback 374 * 375 * @param victim The callback to remove 376 * 377 * @throws IllegalStateException If {@link #isAlive()} returns false 378 * 379 * @deprecated Use #removeOnGlobalLayoutListener instead 380 * 381 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 382 */ 383 @Deprecated removeGlobalOnLayoutListener(OnGlobalLayoutListener victim)384 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { 385 removeOnGlobalLayoutListener(victim); 386 } 387 388 /** 389 * Remove a previously installed global layout callback 390 * 391 * @param victim The callback to remove 392 * 393 * @throws IllegalStateException If {@link #isAlive()} returns false 394 * 395 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 396 */ removeOnGlobalLayoutListener(OnGlobalLayoutListener victim)397 public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) { 398 checkIsAlive(); 399 if (mOnGlobalLayoutListeners == null) { 400 return; 401 } 402 mOnGlobalLayoutListeners.remove(victim); 403 } 404 405 /** 406 * Register a callback to be invoked when the view tree is about to be drawn 407 * 408 * @param listener The callback to add 409 * 410 * @throws IllegalStateException If {@link #isAlive()} returns false 411 */ addOnPreDrawListener(OnPreDrawListener listener)412 public void addOnPreDrawListener(OnPreDrawListener listener) { 413 checkIsAlive(); 414 415 if (mOnPreDrawListeners == null) { 416 mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); 417 } 418 419 mOnPreDrawListeners.add(listener); 420 } 421 422 /** 423 * Remove a previously installed pre-draw callback 424 * 425 * @param victim The callback to remove 426 * 427 * @throws IllegalStateException If {@link #isAlive()} returns false 428 * 429 * @see #addOnPreDrawListener(OnPreDrawListener) 430 */ removeOnPreDrawListener(OnPreDrawListener victim)431 public void removeOnPreDrawListener(OnPreDrawListener victim) { 432 checkIsAlive(); 433 if (mOnPreDrawListeners == null) { 434 return; 435 } 436 mOnPreDrawListeners.remove(victim); 437 } 438 439 /** 440 * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> 441 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 442 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 443 * 444 * @param listener The callback to add 445 * 446 * @throws IllegalStateException If {@link #isAlive()} returns false 447 */ addOnDrawListener(OnDrawListener listener)448 public void addOnDrawListener(OnDrawListener listener) { 449 checkIsAlive(); 450 451 if (mOnDrawListeners == null) { 452 mOnDrawListeners = new ArrayList<OnDrawListener>(); 453 } 454 455 mOnDrawListeners.add(listener); 456 } 457 458 /** 459 * <p>Remove a previously installed pre-draw callback.</p> 460 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 461 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 462 * 463 * @param victim The callback to remove 464 * 465 * @throws IllegalStateException If {@link #isAlive()} returns false 466 * 467 * @see #addOnDrawListener(OnDrawListener) 468 */ removeOnDrawListener(OnDrawListener victim)469 public void removeOnDrawListener(OnDrawListener victim) { 470 checkIsAlive(); 471 if (mOnDrawListeners == null) { 472 return; 473 } 474 mOnDrawListeners.remove(victim); 475 } 476 477 /** 478 * Register a callback to be invoked when a view has been scrolled. 479 * 480 * @param listener The callback to add 481 * 482 * @throws IllegalStateException If {@link #isAlive()} returns false 483 */ addOnScrollChangedListener(OnScrollChangedListener listener)484 public void addOnScrollChangedListener(OnScrollChangedListener listener) { 485 checkIsAlive(); 486 487 if (mOnScrollChangedListeners == null) { 488 mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>(); 489 } 490 491 mOnScrollChangedListeners.add(listener); 492 } 493 494 /** 495 * Remove a previously installed scroll-changed callback 496 * 497 * @param victim The callback to remove 498 * 499 * @throws IllegalStateException If {@link #isAlive()} returns false 500 * 501 * @see #addOnScrollChangedListener(OnScrollChangedListener) 502 */ removeOnScrollChangedListener(OnScrollChangedListener victim)503 public void removeOnScrollChangedListener(OnScrollChangedListener victim) { 504 checkIsAlive(); 505 if (mOnScrollChangedListeners == null) { 506 return; 507 } 508 mOnScrollChangedListeners.remove(victim); 509 } 510 511 /** 512 * Register a callback to be invoked when the invoked when the touch mode changes. 513 * 514 * @param listener The callback to add 515 * 516 * @throws IllegalStateException If {@link #isAlive()} returns false 517 */ addOnTouchModeChangeListener(OnTouchModeChangeListener listener)518 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) { 519 checkIsAlive(); 520 521 if (mOnTouchModeChangeListeners == null) { 522 mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); 523 } 524 525 mOnTouchModeChangeListeners.add(listener); 526 } 527 528 /** 529 * Remove a previously installed touch mode change callback 530 * 531 * @param victim The callback to remove 532 * 533 * @throws IllegalStateException If {@link #isAlive()} returns false 534 * 535 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener) 536 */ removeOnTouchModeChangeListener(OnTouchModeChangeListener victim)537 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) { 538 checkIsAlive(); 539 if (mOnTouchModeChangeListeners == null) { 540 return; 541 } 542 mOnTouchModeChangeListeners.remove(victim); 543 } 544 545 /** 546 * Register a callback to be invoked when the invoked when it is time to 547 * compute the window's internal insets. 548 * 549 * @param listener The callback to add 550 * 551 * @throws IllegalStateException If {@link #isAlive()} returns false 552 * 553 * We are not yet ready to commit to this API and support it, so 554 * @hide 555 */ addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener)556 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) { 557 checkIsAlive(); 558 559 if (mOnComputeInternalInsetsListeners == null) { 560 mOnComputeInternalInsetsListeners = 561 new CopyOnWriteArrayList<OnComputeInternalInsetsListener>(); 562 } 563 564 mOnComputeInternalInsetsListeners.add(listener); 565 } 566 567 /** 568 * Remove a previously installed internal insets computation callback 569 * 570 * @param victim The callback to remove 571 * 572 * @throws IllegalStateException If {@link #isAlive()} returns false 573 * 574 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener) 575 * 576 * We are not yet ready to commit to this API and support it, so 577 * @hide 578 */ removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim)579 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) { 580 checkIsAlive(); 581 if (mOnComputeInternalInsetsListeners == null) { 582 return; 583 } 584 mOnComputeInternalInsetsListeners.remove(victim); 585 } 586 checkIsAlive()587 private void checkIsAlive() { 588 if (!mAlive) { 589 throw new IllegalStateException("This ViewTreeObserver is not alive, call " 590 + "getViewTreeObserver() again"); 591 } 592 } 593 594 /** 595 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive, 596 * any call to a method (except this one) will throw an exception. 597 * 598 * If an application keeps a long-lived reference to this ViewTreeObserver, it should 599 * always check for the result of this method before calling any other method. 600 * 601 * @return True if this object is alive and be used, false otherwise. 602 */ isAlive()603 public boolean isAlive() { 604 return mAlive; 605 } 606 607 /** 608 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking 609 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception. 610 * 611 * @hide 612 */ kill()613 private void kill() { 614 mAlive = false; 615 } 616 617 /** 618 * Notifies registered listeners that focus has changed. 619 */ dispatchOnGlobalFocusChange(View oldFocus, View newFocus)620 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { 621 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 622 // perform the dispatching. The iterator is a safe guard against listeners that 623 // could mutate the list by calling the various add/remove methods. This prevents 624 // the array from being modified while we iterate it. 625 final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; 626 if (listeners != null && listeners.size() > 0) { 627 for (OnGlobalFocusChangeListener listener : listeners) { 628 listener.onGlobalFocusChanged(oldFocus, newFocus); 629 } 630 } 631 } 632 633 /** 634 * Notifies registered listeners that a global layout happened. This can be called 635 * manually if you are forcing a layout on a View or a hierarchy of Views that are 636 * not attached to a Window or in the GONE state. 637 */ dispatchOnGlobalLayout()638 public final void dispatchOnGlobalLayout() { 639 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 640 // perform the dispatching. The iterator is a safe guard against listeners that 641 // could mutate the list by calling the various add/remove methods. This prevents 642 // the array from being modified while we iterate it. 643 final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; 644 if (listeners != null && listeners.size() > 0) { 645 for (OnGlobalLayoutListener listener : listeners) { 646 listener.onGlobalLayout(); 647 } 648 } 649 } 650 651 /** 652 * Notifies registered listeners that the drawing pass is about to start. If a 653 * listener returns true, then the drawing pass is canceled and rescheduled. This can 654 * be called manually if you are forcing the drawing on a View or a hierarchy of Views 655 * that are not attached to a Window or in the GONE state. 656 * 657 * @return True if the current draw should be canceled and resceduled, false otherwise. 658 */ 659 @SuppressWarnings("unchecked") dispatchOnPreDraw()660 public final boolean dispatchOnPreDraw() { 661 // NOTE: we *must* clone the listener list to perform the dispatching. 662 // The clone is a safe guard against listeners that 663 // could mutate the list by calling the various add/remove methods. This prevents 664 // the array from being modified while we process it. 665 boolean cancelDraw = false; 666 if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) { 667 final ArrayList<OnPreDrawListener> listeners = 668 (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone(); 669 int numListeners = listeners.size(); 670 for (int i = 0; i < numListeners; ++i) { 671 cancelDraw |= !(listeners.get(i).onPreDraw()); 672 } 673 } 674 return cancelDraw; 675 } 676 677 /** 678 * Notifies registered listeners that the drawing pass is about to start. 679 */ dispatchOnDraw()680 public final void dispatchOnDraw() { 681 if (mOnDrawListeners != null) { 682 final ArrayList<OnDrawListener> listeners = mOnDrawListeners; 683 int numListeners = listeners.size(); 684 for (int i = 0; i < numListeners; ++i) { 685 listeners.get(i).onDraw(); 686 } 687 } 688 } 689 690 /** 691 * Notifies registered listeners that the touch mode has changed. 692 * 693 * @param inTouchMode True if the touch mode is now enabled, false otherwise. 694 */ dispatchOnTouchModeChanged(boolean inTouchMode)695 final void dispatchOnTouchModeChanged(boolean inTouchMode) { 696 final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = 697 mOnTouchModeChangeListeners; 698 if (listeners != null && listeners.size() > 0) { 699 for (OnTouchModeChangeListener listener : listeners) { 700 listener.onTouchModeChanged(inTouchMode); 701 } 702 } 703 } 704 705 /** 706 * Notifies registered listeners that something has scrolled. 707 */ dispatchOnScrollChanged()708 final void dispatchOnScrollChanged() { 709 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 710 // perform the dispatching. The iterator is a safe guard against listeners that 711 // could mutate the list by calling the various add/remove methods. This prevents 712 // the array from being modified while we iterate it. 713 final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; 714 if (listeners != null && listeners.size() > 0) { 715 for (OnScrollChangedListener listener : listeners) { 716 listener.onScrollChanged(); 717 } 718 } 719 } 720 721 /** 722 * Returns whether there are listeners for computing internal insets. 723 */ hasComputeInternalInsetsListeners()724 final boolean hasComputeInternalInsetsListeners() { 725 final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = 726 mOnComputeInternalInsetsListeners; 727 return (listeners != null && listeners.size() > 0); 728 } 729 730 /** 731 * Calls all listeners to compute the current insets. 732 */ dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo)733 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) { 734 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 735 // perform the dispatching. The iterator is a safe guard against listeners that 736 // could mutate the list by calling the various add/remove methods. This prevents 737 // the array from being modified while we iterate it. 738 final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = 739 mOnComputeInternalInsetsListeners; 740 if (listeners != null && listeners.size() > 0) { 741 for (OnComputeInternalInsetsListener listener : listeners) { 742 listener.onComputeInternalInsets(inoutInfo); 743 } 744 } 745 } 746 } 747