blob: 072c95f491f770759959b24333c0daa0266b392b [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.view;
18
19import android.graphics.Rect;
Jeff Brownfbf09772011-01-16 14:06:57 -080020import android.graphics.Region;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021
Chet Haase6e0ecb42010-11-03 19:41:18 -070022import java.util.ArrayList;
Chet Haase0f8ffd82012-06-07 07:48:48 -070023import java.util.concurrent.CopyOnWriteArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024
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 */
34public final class ViewTreeObserver {
Romain Guyc39ed4a2012-06-12 12:06:46 -070035 // Recursive listeners use CopyOnWriteArrayList
Dianne Hackborn961cae92013-03-20 14:59:43 -070036 private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
37 private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
Chet Haase0f8ffd82012-06-07 07:48:48 -070038 private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
Chet Haase0f8ffd82012-06-07 07:48:48 -070039 private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
Romain Guyc39ed4a2012-06-12 12:06:46 -070040
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
Romain Guy25eba5c2012-04-04 17:29:03 -070049 private ArrayList<OnDrawListener> mOnDrawListeners;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
51 private boolean mAlive = true;
52
53 /**
Dianne Hackborn961cae92013-03-20 14:59:43 -070054 * 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 */
61 public void onWindowAttached();
62
63 /**
64 * Callback method to be invoked when the view hierarchy is detached from a window
65 */
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 */
80 public void onWindowFocusChanged(boolean hasFocus);
81 }
82
83 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 * 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 */
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 */
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 */
128 public boolean onPreDraw();
129 }
130
131 /**
Romain Guy25eba5c2012-04-04 17:29:03 -0700132 * 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 */
149 public void onDraw();
150 }
151
152 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 * 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 */
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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 */
168 public interface OnScrollChangedListener {
169 /**
170 * Callback method to be invoked when something in the view tree
171 * has been scrolled.
172 */
173 public void onScrollChanged();
174 }
175
176 /**
177 * Parameters used with OnComputeInternalInsetsListener.
Dianne Hackborn935ae462009-04-13 16:11:55 -0700178 *
179 * We are not yet ready to commit to this API and support it, so
180 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 */
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();
Romain Guyc39ed4a2012-06-12 12:06:46 -0700188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 /**
Jeff Brownfbf09772011-01-16 14:06:57 -0800190 * Offsets from the frame of the window at which windows behind it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 * are visible.
192 */
193 public final Rect visibleInsets = new Rect();
Jeff Brownfbf09772011-01-16 14:06:57 -0800194
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 /**
203 * Option for {@link #setTouchableInsets(int)}: the entire window frame
204 * can be touched.
205 */
206 public static final int TOUCHABLE_INSETS_FRAME = 0;
Romain Guyc39ed4a2012-06-12 12:06:46 -0700207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 /**
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;
Romain Guyc39ed4a2012-06-12 12:06:46 -0700213
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 /**
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;
Jeff Brownfbf09772011-01-16 14:06:57 -0800219
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 /**
227 * Set which parts of the window can be touched: either
228 * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
Jeff Brownfbf09772011-01-16 14:06:57 -0800229 * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 */
231 public void setTouchableInsets(int val) {
232 mTouchableInsets = val;
233 }
Romain Guy25eba5c2012-04-04 17:29:03 -0700234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 int mTouchableInsets;
Romain Guyc39ed4a2012-06-12 12:06:46 -0700236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 void reset() {
Jeff Brownfbf09772011-01-16 14:06:57 -0800238 contentInsets.setEmpty();
239 visibleInsets.setEmpty();
240 touchableRegion.setEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 mTouchableInsets = TOUCHABLE_INSETS_FRAME;
242 }
Romain Guy25eba5c2012-04-04 17:29:03 -0700243
244 @Override
245 public int hashCode() {
246 int result = contentInsets != null ? contentInsets.hashCode() : 0;
247 result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0);
248 result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0);
249 result = 31 * result + mTouchableInsets;
250 return result;
251 }
252
Romain Guy1e878d22012-01-23 15:34:25 -0800253 @Override
254 public boolean equals(Object o) {
Romain Guy25eba5c2012-04-04 17:29:03 -0700255 if (this == o) return true;
256 if (o == null || getClass() != o.getClass()) return false;
257
258 InternalInsetsInfo other = (InternalInsetsInfo)o;
259 return mTouchableInsets == other.mTouchableInsets &&
260 contentInsets.equals(other.contentInsets) &&
261 visibleInsets.equals(other.visibleInsets) &&
262 touchableRegion.equals(other.touchableRegion);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 }
Romain Guy25eba5c2012-04-04 17:29:03 -0700264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 void set(InternalInsetsInfo other) {
266 contentInsets.set(other.contentInsets);
267 visibleInsets.set(other.visibleInsets);
Jeff Brownfbf09772011-01-16 14:06:57 -0800268 touchableRegion.set(other.touchableRegion);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 mTouchableInsets = other.mTouchableInsets;
270 }
271 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700272
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 /**
274 * Interface definition for a callback to be invoked when layout has
275 * completed and the client can compute its interior insets.
Dianne Hackborn935ae462009-04-13 16:11:55 -0700276 *
277 * We are not yet ready to commit to this API and support it, so
278 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 */
280 public interface OnComputeInternalInsetsListener {
281 /**
282 * Callback method to be invoked when layout has completed and the
283 * client can compute its interior insets.
284 *
285 * @param inoutInfo Should be filled in by the implementation with
286 * the information about the insets of the window. This is called
287 * with whatever values the previous OnComputeInternalInsetsListener
288 * returned, if there are multiple such listeners in the window.
289 */
290 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
291 }
292
293 /**
294 * Creates a new ViewTreeObserver. This constructor should not be called
295 */
296 ViewTreeObserver() {
297 }
298
299 /**
300 * Merges all the listeners registered on the specified observer with the listeners
301 * registered on this object. After this method is invoked, the specified observer
302 * will return false in {@link #isAlive()} and should not be used anymore.
303 *
304 * @param observer The ViewTreeObserver whose listeners must be added to this observer
305 */
306 void merge(ViewTreeObserver observer) {
Dianne Hackborn961cae92013-03-20 14:59:43 -0700307 if (observer.mOnWindowAttachListeners != null) {
308 if (mOnWindowAttachListeners != null) {
309 mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
310 } else {
311 mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
312 }
313 }
314
315 if (observer.mOnWindowFocusListeners != null) {
316 if (mOnWindowFocusListeners != null) {
317 mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
318 } else {
319 mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
320 }
321 }
322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 if (observer.mOnGlobalFocusListeners != null) {
324 if (mOnGlobalFocusListeners != null) {
325 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
326 } else {
327 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
328 }
329 }
330
331 if (observer.mOnGlobalLayoutListeners != null) {
332 if (mOnGlobalLayoutListeners != null) {
333 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
334 } else {
335 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
336 }
337 }
338
339 if (observer.mOnPreDrawListeners != null) {
340 if (mOnPreDrawListeners != null) {
341 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
342 } else {
343 mOnPreDrawListeners = observer.mOnPreDrawListeners;
344 }
345 }
346
347 if (observer.mOnTouchModeChangeListeners != null) {
348 if (mOnTouchModeChangeListeners != null) {
349 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
350 } else {
351 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
352 }
353 }
354
355 if (observer.mOnComputeInternalInsetsListeners != null) {
356 if (mOnComputeInternalInsetsListeners != null) {
357 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
358 } else {
359 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
360 }
361 }
362
Mark Brophy757c6972011-10-25 17:01:28 +0100363 if (observer.mOnScrollChangedListeners != null) {
364 if (mOnScrollChangedListeners != null) {
365 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
366 } else {
367 mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
368 }
369 }
370
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 observer.kill();
372 }
373
374 /**
Dianne Hackborn961cae92013-03-20 14:59:43 -0700375 * Register a callback to be invoked when the view hierarchy is attached to a window.
376 *
377 * @param listener The callback to add
378 *
379 * @throws IllegalStateException If {@link #isAlive()} returns false
380 */
381 public void addOnWindowAttachListener(OnWindowAttachListener listener) {
382 checkIsAlive();
383
384 if (mOnWindowAttachListeners == null) {
385 mOnWindowAttachListeners
386 = new CopyOnWriteArrayList<OnWindowAttachListener>();
387 }
388
389 mOnWindowAttachListeners.add(listener);
390 }
391
392 /**
393 * Remove a previously installed window attach callback.
394 *
395 * @param victim The callback to remove
396 *
397 * @throws IllegalStateException If {@link #isAlive()} returns false
398 *
399 * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
400 */
401 public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
402 checkIsAlive();
403 if (mOnWindowAttachListeners == null) {
404 return;
405 }
406 mOnWindowAttachListeners.remove(victim);
407 }
408
409 /**
410 * Register a callback to be invoked when the window focus state within the view tree changes.
411 *
412 * @param listener The callback to add
413 *
414 * @throws IllegalStateException If {@link #isAlive()} returns false
415 */
416 public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
417 checkIsAlive();
418
419 if (mOnWindowFocusListeners == null) {
420 mOnWindowFocusListeners
421 = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
422 }
423
424 mOnWindowFocusListeners.add(listener);
425 }
426
427 /**
428 * Remove a previously installed window focus change callback.
429 *
430 * @param victim The callback to remove
431 *
432 * @throws IllegalStateException If {@link #isAlive()} returns false
433 *
434 * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
435 */
436 public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
437 checkIsAlive();
438 if (mOnWindowFocusListeners == null) {
439 return;
440 }
441 mOnWindowFocusListeners.remove(victim);
442 }
443
444 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 * Register a callback to be invoked when the focus state within the view tree changes.
446 *
447 * @param listener The callback to add
448 *
449 * @throws IllegalStateException If {@link #isAlive()} returns false
450 */
451 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
452 checkIsAlive();
453
454 if (mOnGlobalFocusListeners == null) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700455 mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 }
457
458 mOnGlobalFocusListeners.add(listener);
459 }
460
461 /**
462 * Remove a previously installed focus change callback.
463 *
464 * @param victim The callback to remove
465 *
466 * @throws IllegalStateException If {@link #isAlive()} returns false
467 *
468 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
469 */
470 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
471 checkIsAlive();
472 if (mOnGlobalFocusListeners == null) {
473 return;
474 }
475 mOnGlobalFocusListeners.remove(victim);
476 }
477
478 /**
479 * Register a callback to be invoked when the global layout state or the visibility of views
480 * within the view tree changes
481 *
482 * @param listener The callback to add
483 *
484 * @throws IllegalStateException If {@link #isAlive()} returns false
485 */
486 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
487 checkIsAlive();
488
489 if (mOnGlobalLayoutListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700490 mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 }
492
493 mOnGlobalLayoutListeners.add(listener);
494 }
495
496 /**
497 * Remove a previously installed global layout callback
498 *
499 * @param victim The callback to remove
500 *
501 * @throws IllegalStateException If {@link #isAlive()} returns false
Romain Guy1e878d22012-01-23 15:34:25 -0800502 *
503 * @deprecated Use #removeOnGlobalLayoutListener instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 *
505 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
506 */
Romain Guy1e878d22012-01-23 15:34:25 -0800507 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
Romain Guy1e878d22012-01-23 15:34:25 -0800509 removeOnGlobalLayoutListener(victim);
510 }
511
512 /**
513 * Remove a previously installed global layout callback
514 *
515 * @param victim The callback to remove
516 *
517 * @throws IllegalStateException If {@link #isAlive()} returns false
518 *
519 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
520 */
521 public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 checkIsAlive();
523 if (mOnGlobalLayoutListeners == null) {
524 return;
525 }
526 mOnGlobalLayoutListeners.remove(victim);
527 }
528
529 /**
530 * Register a callback to be invoked when the view tree is about to be drawn
531 *
532 * @param listener The callback to add
533 *
534 * @throws IllegalStateException If {@link #isAlive()} returns false
535 */
536 public void addOnPreDrawListener(OnPreDrawListener listener) {
537 checkIsAlive();
538
539 if (mOnPreDrawListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700540 mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 }
542
543 mOnPreDrawListeners.add(listener);
544 }
545
546 /**
547 * Remove a previously installed pre-draw callback
548 *
549 * @param victim The callback to remove
550 *
551 * @throws IllegalStateException If {@link #isAlive()} returns false
552 *
553 * @see #addOnPreDrawListener(OnPreDrawListener)
554 */
555 public void removeOnPreDrawListener(OnPreDrawListener victim) {
556 checkIsAlive();
557 if (mOnPreDrawListeners == null) {
558 return;
559 }
560 mOnPreDrawListeners.remove(victim);
561 }
562
563 /**
Romain Guy25eba5c2012-04-04 17:29:03 -0700564 * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
565 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
566 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
567 *
568 * @param listener The callback to add
569 *
570 * @throws IllegalStateException If {@link #isAlive()} returns false
571 */
572 public void addOnDrawListener(OnDrawListener listener) {
573 checkIsAlive();
574
575 if (mOnDrawListeners == null) {
576 mOnDrawListeners = new ArrayList<OnDrawListener>();
577 }
578
579 mOnDrawListeners.add(listener);
580 }
581
582 /**
583 * <p>Remove a previously installed pre-draw callback.</p>
584 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
585 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
586 *
587 * @param victim The callback to remove
588 *
589 * @throws IllegalStateException If {@link #isAlive()} returns false
590 *
591 * @see #addOnDrawListener(OnDrawListener)
592 */
593 public void removeOnDrawListener(OnDrawListener victim) {
594 checkIsAlive();
595 if (mOnDrawListeners == null) {
596 return;
597 }
598 mOnDrawListeners.remove(victim);
599 }
600
601 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 * Register a callback to be invoked when a view has been scrolled.
603 *
604 * @param listener The callback to add
605 *
606 * @throws IllegalStateException If {@link #isAlive()} returns false
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 */
608 public void addOnScrollChangedListener(OnScrollChangedListener listener) {
609 checkIsAlive();
610
611 if (mOnScrollChangedListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700612 mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 }
614
615 mOnScrollChangedListeners.add(listener);
616 }
617
618 /**
619 * Remove a previously installed scroll-changed callback
620 *
621 * @param victim The callback to remove
622 *
623 * @throws IllegalStateException If {@link #isAlive()} returns false
624 *
625 * @see #addOnScrollChangedListener(OnScrollChangedListener)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 */
627 public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
628 checkIsAlive();
629 if (mOnScrollChangedListeners == null) {
630 return;
631 }
632 mOnScrollChangedListeners.remove(victim);
633 }
634
635 /**
636 * Register a callback to be invoked when the invoked when the touch mode changes.
637 *
638 * @param listener The callback to add
639 *
640 * @throws IllegalStateException If {@link #isAlive()} returns false
641 */
642 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
643 checkIsAlive();
644
645 if (mOnTouchModeChangeListeners == null) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700646 mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 }
648
649 mOnTouchModeChangeListeners.add(listener);
650 }
651
652 /**
653 * Remove a previously installed touch mode change callback
654 *
655 * @param victim The callback to remove
656 *
657 * @throws IllegalStateException If {@link #isAlive()} returns false
658 *
659 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
660 */
661 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
662 checkIsAlive();
663 if (mOnTouchModeChangeListeners == null) {
664 return;
665 }
666 mOnTouchModeChangeListeners.remove(victim);
667 }
668
669 /**
670 * Register a callback to be invoked when the invoked when it is time to
671 * compute the window's internal insets.
672 *
673 * @param listener The callback to add
674 *
675 * @throws IllegalStateException If {@link #isAlive()} returns false
Dianne Hackborn935ae462009-04-13 16:11:55 -0700676 *
677 * We are not yet ready to commit to this API and support it, so
678 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 */
680 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
681 checkIsAlive();
682
683 if (mOnComputeInternalInsetsListeners == null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700684 mOnComputeInternalInsetsListeners =
Romain Guyc39ed4a2012-06-12 12:06:46 -0700685 new CopyOnWriteArray<OnComputeInternalInsetsListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 }
687
688 mOnComputeInternalInsetsListeners.add(listener);
689 }
690
691 /**
692 * Remove a previously installed internal insets computation callback
693 *
694 * @param victim The callback to remove
695 *
696 * @throws IllegalStateException If {@link #isAlive()} returns false
697 *
698 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
Dianne Hackborn935ae462009-04-13 16:11:55 -0700699 *
700 * We are not yet ready to commit to this API and support it, so
701 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702 */
703 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
704 checkIsAlive();
705 if (mOnComputeInternalInsetsListeners == null) {
706 return;
707 }
708 mOnComputeInternalInsetsListeners.remove(victim);
709 }
710
711 private void checkIsAlive() {
712 if (!mAlive) {
713 throw new IllegalStateException("This ViewTreeObserver is not alive, call "
714 + "getViewTreeObserver() again");
715 }
716 }
717
718 /**
719 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
720 * any call to a method (except this one) will throw an exception.
721 *
722 * If an application keeps a long-lived reference to this ViewTreeObserver, it should
723 * always check for the result of this method before calling any other method.
724 *
725 * @return True if this object is alive and be used, false otherwise.
726 */
727 public boolean isAlive() {
728 return mAlive;
729 }
730
731 /**
732 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
733 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
734 *
735 * @hide
736 */
737 private void kill() {
738 mAlive = false;
739 }
740
741 /**
Dianne Hackborn961cae92013-03-20 14:59:43 -0700742 * Notifies registered listeners that window has been attached/detached.
743 */
744 final void dispatchOnWindowAttachedChange(boolean attached) {
745 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
746 // perform the dispatching. The iterator is a safe guard against listeners that
747 // could mutate the list by calling the various add/remove methods. This prevents
748 // the array from being modified while we iterate it.
749 final CopyOnWriteArrayList<OnWindowAttachListener> listeners
750 = mOnWindowAttachListeners;
751 if (listeners != null && listeners.size() > 0) {
752 for (OnWindowAttachListener listener : listeners) {
753 if (attached) listener.onWindowAttached();
754 else listener.onWindowDetached();
755 }
756 }
757 }
758
759 /**
760 * Notifies registered listeners that window focus has changed.
761 */
762 final void dispatchOnWindowFocusChange(boolean hasFocus) {
763 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
764 // perform the dispatching. The iterator is a safe guard against listeners that
765 // could mutate the list by calling the various add/remove methods. This prevents
766 // the array from being modified while we iterate it.
767 final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
768 = mOnWindowFocusListeners;
769 if (listeners != null && listeners.size() > 0) {
770 for (OnWindowFocusChangeListener listener : listeners) {
771 listener.onWindowFocusChanged(hasFocus);
772 }
773 }
774 }
775
776 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777 * Notifies registered listeners that focus has changed.
778 */
779 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700780 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
781 // perform the dispatching. The iterator is a safe guard against listeners that
782 // could mutate the list by calling the various add/remove methods. This prevents
783 // the array from being modified while we iterate it.
Chet Haase0f8ffd82012-06-07 07:48:48 -0700784 final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700785 if (listeners != null && listeners.size() > 0) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700786 for (OnGlobalFocusChangeListener listener : listeners) {
787 listener.onGlobalFocusChanged(oldFocus, newFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 }
789 }
790 }
791
792 /**
793 * Notifies registered listeners that a global layout happened. This can be called
794 * manually if you are forcing a layout on a View or a hierarchy of Views that are
795 * not attached to a Window or in the GONE state.
796 */
797 public final void dispatchOnGlobalLayout() {
The Android Open Source Project10592532009-03-18 17:39:46 -0700798 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
799 // perform the dispatching. The iterator is a safe guard against listeners that
800 // could mutate the list by calling the various add/remove methods. This prevents
801 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700802 final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700803 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700804 CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
805 try {
806 int count = access.size();
807 for (int i = 0; i < count; i++) {
808 access.get(i).onGlobalLayout();
809 }
810 } finally {
811 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 }
813 }
814 }
815
816 /**
817 * Notifies registered listeners that the drawing pass is about to start. If a
818 * listener returns true, then the drawing pass is canceled and rescheduled. This can
819 * be called manually if you are forcing the drawing on a View or a hierarchy of Views
820 * that are not attached to a Window or in the GONE state.
821 *
822 * @return True if the current draw should be canceled and resceduled, false otherwise.
823 */
Romain Guy25eba5c2012-04-04 17:29:03 -0700824 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 public final boolean dispatchOnPreDraw() {
826 boolean cancelDraw = false;
Romain Guyc39ed4a2012-06-12 12:06:46 -0700827 final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
828 if (listeners != null && listeners.size() > 0) {
829 CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
830 try {
831 int count = access.size();
832 for (int i = 0; i < count; i++) {
833 cancelDraw |= !(access.get(i).onPreDraw());
834 }
835 } finally {
836 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 }
838 }
839 return cancelDraw;
840 }
841
842 /**
Romain Guy25eba5c2012-04-04 17:29:03 -0700843 * Notifies registered listeners that the drawing pass is about to start.
844 */
845 public final void dispatchOnDraw() {
846 if (mOnDrawListeners != null) {
847 final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
848 int numListeners = listeners.size();
849 for (int i = 0; i < numListeners; ++i) {
850 listeners.get(i).onDraw();
851 }
852 }
853 }
854
855 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 * Notifies registered listeners that the touch mode has changed.
857 *
858 * @param inTouchMode True if the touch mode is now enabled, false otherwise.
859 */
860 final void dispatchOnTouchModeChanged(boolean inTouchMode) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700861 final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700862 mOnTouchModeChangeListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700863 if (listeners != null && listeners.size() > 0) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700864 for (OnTouchModeChangeListener listener : listeners) {
865 listener.onTouchModeChanged(inTouchMode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867 }
868 }
869
870 /**
871 * Notifies registered listeners that something has scrolled.
872 */
873 final void dispatchOnScrollChanged() {
The Android Open Source Project10592532009-03-18 17:39:46 -0700874 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
875 // perform the dispatching. The iterator is a safe guard against listeners that
876 // could mutate the list by calling the various add/remove methods. This prevents
877 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700878 final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700879 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700880 CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
881 try {
882 int count = access.size();
883 for (int i = 0; i < count; i++) {
884 access.get(i).onScrollChanged();
885 }
886 } finally {
887 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889 }
890 }
891
892 /**
893 * Returns whether there are listeners for computing internal insets.
894 */
895 final boolean hasComputeInternalInsetsListeners() {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700896 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700897 mOnComputeInternalInsetsListeners;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 return (listeners != null && listeners.size() > 0);
899 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700900
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901 /**
902 * Calls all listeners to compute the current insets.
903 */
904 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700905 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
906 // perform the dispatching. The iterator is a safe guard against listeners that
907 // could mutate the list by calling the various add/remove methods. This prevents
908 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700909 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700910 mOnComputeInternalInsetsListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700911 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700912 CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
913 try {
914 int count = access.size();
915 for (int i = 0; i < count; i++) {
916 access.get(i).onComputeInternalInsets(inoutInfo);
917 }
918 } finally {
919 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800920 }
921 }
922 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700923
924 /**
925 * Copy on write array. This array is not thread safe, and only one loop can
926 * iterate over this array at any given time. This class avoids allocations
927 * until a concurrent modification happens.
928 *
929 * Usage:
930 *
931 * CopyOnWriteArray.Access<MyData> access = array.start();
932 * try {
933 * for (int i = 0; i < access.size(); i++) {
934 * MyData d = access.get(i);
935 * }
936 * } finally {
937 * access.end();
938 * }
939 */
940 static class CopyOnWriteArray<T> {
941 private ArrayList<T> mData = new ArrayList<T>();
942 private ArrayList<T> mDataCopy;
943
944 private final Access<T> mAccess = new Access<T>();
945
946 private boolean mStart;
947
948 static class Access<T> {
949 private ArrayList<T> mData;
950 private int mSize;
951
952 T get(int index) {
953 return mData.get(index);
954 }
955
956 int size() {
957 return mSize;
958 }
959 }
960
961 CopyOnWriteArray() {
962 }
963
964 private ArrayList<T> getArray() {
965 if (mStart) {
966 if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
967 return mDataCopy;
968 }
969 return mData;
970 }
971
972 Access<T> start() {
973 if (mStart) throw new IllegalStateException("Iteration already started");
974 mStart = true;
975 mDataCopy = null;
976 mAccess.mData = mData;
977 mAccess.mSize = mData.size();
978 return mAccess;
979 }
980
981 void end() {
982 if (!mStart) throw new IllegalStateException("Iteration not started");
983 mStart = false;
984 if (mDataCopy != null) {
985 mData = mDataCopy;
986 }
987 mDataCopy = null;
988 }
989
990 int size() {
991 return getArray().size();
992 }
993
994 void add(T item) {
995 getArray().add(item);
996 }
997
998 void addAll(CopyOnWriteArray<T> array) {
999 getArray().addAll(array.mData);
1000 }
1001
1002 void remove(T item) {
1003 getArray().remove(item);
1004 }
1005
1006 void clear() {
1007 getArray().clear();
1008 }
1009 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001010}