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