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