blob: a9444b44760ad4bdba987281a0867c2e422b20c7 [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
Jeff Brown2e05ec32013-09-30 15:57:43 -0700244 boolean isEmpty() {
245 return contentInsets.isEmpty()
246 && visibleInsets.isEmpty()
247 && touchableRegion.isEmpty()
248 && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
249 }
250
Romain Guy25eba5c2012-04-04 17:29:03 -0700251 @Override
252 public int hashCode() {
Romain Guy21f42302013-06-28 19:19:30 -0700253 int result = contentInsets.hashCode();
254 result = 31 * result + visibleInsets.hashCode();
255 result = 31 * result + touchableRegion.hashCode();
Romain Guy25eba5c2012-04-04 17:29:03 -0700256 result = 31 * result + mTouchableInsets;
257 return result;
258 }
259
Romain Guy1e878d22012-01-23 15:34:25 -0800260 @Override
261 public boolean equals(Object o) {
Romain Guy25eba5c2012-04-04 17:29:03 -0700262 if (this == o) return true;
263 if (o == null || getClass() != o.getClass()) return false;
264
265 InternalInsetsInfo other = (InternalInsetsInfo)o;
266 return mTouchableInsets == other.mTouchableInsets &&
267 contentInsets.equals(other.contentInsets) &&
268 visibleInsets.equals(other.visibleInsets) &&
269 touchableRegion.equals(other.touchableRegion);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 }
Romain Guy25eba5c2012-04-04 17:29:03 -0700271
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 void set(InternalInsetsInfo other) {
273 contentInsets.set(other.contentInsets);
274 visibleInsets.set(other.visibleInsets);
Jeff Brownfbf09772011-01-16 14:06:57 -0800275 touchableRegion.set(other.touchableRegion);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 mTouchableInsets = other.mTouchableInsets;
277 }
278 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 /**
281 * Interface definition for a callback to be invoked when layout has
282 * completed and the client can compute its interior insets.
Dianne Hackborn935ae462009-04-13 16:11:55 -0700283 *
284 * We are not yet ready to commit to this API and support it, so
285 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 */
287 public interface OnComputeInternalInsetsListener {
288 /**
289 * Callback method to be invoked when layout has completed and the
290 * client can compute its interior insets.
291 *
292 * @param inoutInfo Should be filled in by the implementation with
293 * the information about the insets of the window. This is called
294 * with whatever values the previous OnComputeInternalInsetsListener
295 * returned, if there are multiple such listeners in the window.
296 */
297 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
298 }
299
300 /**
301 * Creates a new ViewTreeObserver. This constructor should not be called
302 */
303 ViewTreeObserver() {
304 }
305
306 /**
307 * Merges all the listeners registered on the specified observer with the listeners
308 * registered on this object. After this method is invoked, the specified observer
309 * will return false in {@link #isAlive()} and should not be used anymore.
310 *
311 * @param observer The ViewTreeObserver whose listeners must be added to this observer
312 */
313 void merge(ViewTreeObserver observer) {
Dianne Hackborn961cae92013-03-20 14:59:43 -0700314 if (observer.mOnWindowAttachListeners != null) {
315 if (mOnWindowAttachListeners != null) {
316 mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
317 } else {
318 mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
319 }
320 }
321
322 if (observer.mOnWindowFocusListeners != null) {
323 if (mOnWindowFocusListeners != null) {
324 mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
325 } else {
326 mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
327 }
328 }
329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 if (observer.mOnGlobalFocusListeners != null) {
331 if (mOnGlobalFocusListeners != null) {
332 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
333 } else {
334 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
335 }
336 }
337
338 if (observer.mOnGlobalLayoutListeners != null) {
339 if (mOnGlobalLayoutListeners != null) {
340 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
341 } else {
342 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
343 }
344 }
345
346 if (observer.mOnPreDrawListeners != null) {
347 if (mOnPreDrawListeners != null) {
348 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
349 } else {
350 mOnPreDrawListeners = observer.mOnPreDrawListeners;
351 }
352 }
353
354 if (observer.mOnTouchModeChangeListeners != null) {
355 if (mOnTouchModeChangeListeners != null) {
356 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
357 } else {
358 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
359 }
360 }
361
362 if (observer.mOnComputeInternalInsetsListeners != null) {
363 if (mOnComputeInternalInsetsListeners != null) {
364 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
365 } else {
366 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
367 }
368 }
369
Mark Brophy757c6972011-10-25 17:01:28 +0100370 if (observer.mOnScrollChangedListeners != null) {
371 if (mOnScrollChangedListeners != null) {
372 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
373 } else {
374 mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
375 }
376 }
377
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 observer.kill();
379 }
380
381 /**
Dianne Hackborn961cae92013-03-20 14:59:43 -0700382 * Register a callback to be invoked when the view hierarchy is attached to a window.
383 *
384 * @param listener The callback to add
385 *
386 * @throws IllegalStateException If {@link #isAlive()} returns false
387 */
388 public void addOnWindowAttachListener(OnWindowAttachListener listener) {
389 checkIsAlive();
390
391 if (mOnWindowAttachListeners == null) {
392 mOnWindowAttachListeners
393 = new CopyOnWriteArrayList<OnWindowAttachListener>();
394 }
395
396 mOnWindowAttachListeners.add(listener);
397 }
398
399 /**
400 * Remove a previously installed window attach callback.
401 *
402 * @param victim The callback to remove
403 *
404 * @throws IllegalStateException If {@link #isAlive()} returns false
405 *
406 * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
407 */
408 public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
409 checkIsAlive();
410 if (mOnWindowAttachListeners == null) {
411 return;
412 }
413 mOnWindowAttachListeners.remove(victim);
414 }
415
416 /**
417 * Register a callback to be invoked when the window focus state within the view tree changes.
418 *
419 * @param listener The callback to add
420 *
421 * @throws IllegalStateException If {@link #isAlive()} returns false
422 */
423 public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
424 checkIsAlive();
425
426 if (mOnWindowFocusListeners == null) {
427 mOnWindowFocusListeners
428 = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
429 }
430
431 mOnWindowFocusListeners.add(listener);
432 }
433
434 /**
435 * Remove a previously installed window focus change callback.
436 *
437 * @param victim The callback to remove
438 *
439 * @throws IllegalStateException If {@link #isAlive()} returns false
440 *
441 * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
442 */
443 public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
444 checkIsAlive();
445 if (mOnWindowFocusListeners == null) {
446 return;
447 }
448 mOnWindowFocusListeners.remove(victim);
449 }
450
451 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 * Register a callback to be invoked when the focus state within the view tree changes.
453 *
454 * @param listener The callback to add
455 *
456 * @throws IllegalStateException If {@link #isAlive()} returns false
457 */
458 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
459 checkIsAlive();
460
461 if (mOnGlobalFocusListeners == null) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700462 mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 }
464
465 mOnGlobalFocusListeners.add(listener);
466 }
467
468 /**
469 * Remove a previously installed focus change callback.
470 *
471 * @param victim The callback to remove
472 *
473 * @throws IllegalStateException If {@link #isAlive()} returns false
474 *
475 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
476 */
477 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
478 checkIsAlive();
479 if (mOnGlobalFocusListeners == null) {
480 return;
481 }
482 mOnGlobalFocusListeners.remove(victim);
483 }
484
485 /**
486 * Register a callback to be invoked when the global layout state or the visibility of views
487 * within the view tree changes
488 *
489 * @param listener The callback to add
490 *
491 * @throws IllegalStateException If {@link #isAlive()} returns false
492 */
493 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
494 checkIsAlive();
495
496 if (mOnGlobalLayoutListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700497 mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 }
499
500 mOnGlobalLayoutListeners.add(listener);
501 }
502
503 /**
504 * Remove a previously installed global layout callback
505 *
506 * @param victim The callback to remove
507 *
508 * @throws IllegalStateException If {@link #isAlive()} returns false
Romain Guy1e878d22012-01-23 15:34:25 -0800509 *
510 * @deprecated Use #removeOnGlobalLayoutListener instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 *
512 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
513 */
Romain Guy1e878d22012-01-23 15:34:25 -0800514 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
Romain Guy1e878d22012-01-23 15:34:25 -0800516 removeOnGlobalLayoutListener(victim);
517 }
518
519 /**
520 * Remove a previously installed global layout callback
521 *
522 * @param victim The callback to remove
523 *
524 * @throws IllegalStateException If {@link #isAlive()} returns false
525 *
526 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
527 */
528 public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 checkIsAlive();
530 if (mOnGlobalLayoutListeners == null) {
531 return;
532 }
533 mOnGlobalLayoutListeners.remove(victim);
534 }
535
536 /**
537 * Register a callback to be invoked when the view tree is about to be drawn
538 *
539 * @param listener The callback to add
540 *
541 * @throws IllegalStateException If {@link #isAlive()} returns false
542 */
543 public void addOnPreDrawListener(OnPreDrawListener listener) {
544 checkIsAlive();
545
546 if (mOnPreDrawListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700547 mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
549
550 mOnPreDrawListeners.add(listener);
551 }
552
553 /**
554 * Remove a previously installed pre-draw callback
555 *
556 * @param victim The callback to remove
557 *
558 * @throws IllegalStateException If {@link #isAlive()} returns false
559 *
560 * @see #addOnPreDrawListener(OnPreDrawListener)
561 */
562 public void removeOnPreDrawListener(OnPreDrawListener victim) {
563 checkIsAlive();
564 if (mOnPreDrawListeners == null) {
565 return;
566 }
567 mOnPreDrawListeners.remove(victim);
568 }
569
570 /**
Romain Guy25eba5c2012-04-04 17:29:03 -0700571 * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
572 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
573 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
574 *
575 * @param listener The callback to add
576 *
577 * @throws IllegalStateException If {@link #isAlive()} returns false
578 */
579 public void addOnDrawListener(OnDrawListener listener) {
580 checkIsAlive();
581
582 if (mOnDrawListeners == null) {
583 mOnDrawListeners = new ArrayList<OnDrawListener>();
584 }
585
586 mOnDrawListeners.add(listener);
587 }
588
589 /**
590 * <p>Remove a previously installed pre-draw callback.</p>
591 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
592 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
593 *
594 * @param victim The callback to remove
595 *
596 * @throws IllegalStateException If {@link #isAlive()} returns false
597 *
598 * @see #addOnDrawListener(OnDrawListener)
599 */
600 public void removeOnDrawListener(OnDrawListener victim) {
601 checkIsAlive();
602 if (mOnDrawListeners == null) {
603 return;
604 }
605 mOnDrawListeners.remove(victim);
606 }
607
608 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 * Register a callback to be invoked when a view has been scrolled.
610 *
611 * @param listener The callback to add
612 *
613 * @throws IllegalStateException If {@link #isAlive()} returns false
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 */
615 public void addOnScrollChangedListener(OnScrollChangedListener listener) {
616 checkIsAlive();
617
618 if (mOnScrollChangedListeners == null) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700619 mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 }
621
622 mOnScrollChangedListeners.add(listener);
623 }
624
625 /**
626 * Remove a previously installed scroll-changed callback
627 *
628 * @param victim The callback to remove
629 *
630 * @throws IllegalStateException If {@link #isAlive()} returns false
631 *
632 * @see #addOnScrollChangedListener(OnScrollChangedListener)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 */
634 public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
635 checkIsAlive();
636 if (mOnScrollChangedListeners == null) {
637 return;
638 }
639 mOnScrollChangedListeners.remove(victim);
640 }
641
642 /**
643 * Register a callback to be invoked when the invoked when the touch mode changes.
644 *
645 * @param listener The callback to add
646 *
647 * @throws IllegalStateException If {@link #isAlive()} returns false
648 */
649 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
650 checkIsAlive();
651
652 if (mOnTouchModeChangeListeners == null) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700653 mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 }
655
656 mOnTouchModeChangeListeners.add(listener);
657 }
658
659 /**
660 * Remove a previously installed touch mode change callback
661 *
662 * @param victim The callback to remove
663 *
664 * @throws IllegalStateException If {@link #isAlive()} returns false
665 *
666 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
667 */
668 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
669 checkIsAlive();
670 if (mOnTouchModeChangeListeners == null) {
671 return;
672 }
673 mOnTouchModeChangeListeners.remove(victim);
674 }
675
676 /**
677 * Register a callback to be invoked when the invoked when it is time to
678 * compute the window's internal insets.
679 *
680 * @param listener The callback to add
681 *
682 * @throws IllegalStateException If {@link #isAlive()} returns false
Dianne Hackborn935ae462009-04-13 16:11:55 -0700683 *
684 * We are not yet ready to commit to this API and support it, so
685 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 */
687 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
688 checkIsAlive();
689
690 if (mOnComputeInternalInsetsListeners == null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700691 mOnComputeInternalInsetsListeners =
Romain Guyc39ed4a2012-06-12 12:06:46 -0700692 new CopyOnWriteArray<OnComputeInternalInsetsListener>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 }
694
695 mOnComputeInternalInsetsListeners.add(listener);
696 }
697
698 /**
699 * Remove a previously installed internal insets computation callback
700 *
701 * @param victim The callback to remove
702 *
703 * @throws IllegalStateException If {@link #isAlive()} returns false
704 *
705 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
Dianne Hackborn935ae462009-04-13 16:11:55 -0700706 *
707 * We are not yet ready to commit to this API and support it, so
708 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 */
710 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
711 checkIsAlive();
712 if (mOnComputeInternalInsetsListeners == null) {
713 return;
714 }
715 mOnComputeInternalInsetsListeners.remove(victim);
716 }
717
718 private void checkIsAlive() {
719 if (!mAlive) {
720 throw new IllegalStateException("This ViewTreeObserver is not alive, call "
721 + "getViewTreeObserver() again");
722 }
723 }
724
725 /**
726 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
727 * any call to a method (except this one) will throw an exception.
728 *
729 * If an application keeps a long-lived reference to this ViewTreeObserver, it should
730 * always check for the result of this method before calling any other method.
731 *
732 * @return True if this object is alive and be used, false otherwise.
733 */
734 public boolean isAlive() {
735 return mAlive;
736 }
737
738 /**
739 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
740 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
741 *
742 * @hide
743 */
744 private void kill() {
745 mAlive = false;
746 }
747
748 /**
Dianne Hackborn961cae92013-03-20 14:59:43 -0700749 * Notifies registered listeners that window has been attached/detached.
750 */
751 final void dispatchOnWindowAttachedChange(boolean attached) {
752 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
753 // perform the dispatching. The iterator is a safe guard against listeners that
754 // could mutate the list by calling the various add/remove methods. This prevents
755 // the array from being modified while we iterate it.
756 final CopyOnWriteArrayList<OnWindowAttachListener> listeners
757 = mOnWindowAttachListeners;
758 if (listeners != null && listeners.size() > 0) {
759 for (OnWindowAttachListener listener : listeners) {
760 if (attached) listener.onWindowAttached();
761 else listener.onWindowDetached();
762 }
763 }
764 }
765
766 /**
767 * Notifies registered listeners that window focus has changed.
768 */
769 final void dispatchOnWindowFocusChange(boolean hasFocus) {
770 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
771 // perform the dispatching. The iterator is a safe guard against listeners that
772 // could mutate the list by calling the various add/remove methods. This prevents
773 // the array from being modified while we iterate it.
774 final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
775 = mOnWindowFocusListeners;
776 if (listeners != null && listeners.size() > 0) {
777 for (OnWindowFocusChangeListener listener : listeners) {
778 listener.onWindowFocusChanged(hasFocus);
779 }
780 }
781 }
782
783 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 * Notifies registered listeners that focus has changed.
785 */
786 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700787 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
788 // perform the dispatching. The iterator is a safe guard against listeners that
789 // could mutate the list by calling the various add/remove methods. This prevents
790 // the array from being modified while we iterate it.
Chet Haase0f8ffd82012-06-07 07:48:48 -0700791 final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700792 if (listeners != null && listeners.size() > 0) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700793 for (OnGlobalFocusChangeListener listener : listeners) {
794 listener.onGlobalFocusChanged(oldFocus, newFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 }
796 }
797 }
798
799 /**
800 * Notifies registered listeners that a global layout happened. This can be called
801 * manually if you are forcing a layout on a View or a hierarchy of Views that are
802 * not attached to a Window or in the GONE state.
803 */
804 public final void dispatchOnGlobalLayout() {
The Android Open Source Project10592532009-03-18 17:39:46 -0700805 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
806 // perform the dispatching. The iterator is a safe guard against listeners that
807 // could mutate the list by calling the various add/remove methods. This prevents
808 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700809 final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700810 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700811 CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
812 try {
813 int count = access.size();
814 for (int i = 0; i < count; i++) {
815 access.get(i).onGlobalLayout();
816 }
817 } finally {
818 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 }
820 }
821 }
822
823 /**
Romain Guy21f42302013-06-28 19:19:30 -0700824 * Returns whether there are listeners for on pre-draw events.
825 */
826 final boolean hasOnPreDrawListeners() {
827 return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
828 }
829
830 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831 * Notifies registered listeners that the drawing pass is about to start. If a
832 * listener returns true, then the drawing pass is canceled and rescheduled. This can
833 * be called manually if you are forcing the drawing on a View or a hierarchy of Views
834 * that are not attached to a Window or in the GONE state.
835 *
836 * @return True if the current draw should be canceled and resceduled, false otherwise.
837 */
Romain Guy25eba5c2012-04-04 17:29:03 -0700838 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 public final boolean dispatchOnPreDraw() {
840 boolean cancelDraw = false;
Romain Guyc39ed4a2012-06-12 12:06:46 -0700841 final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
842 if (listeners != null && listeners.size() > 0) {
843 CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
844 try {
845 int count = access.size();
846 for (int i = 0; i < count; i++) {
847 cancelDraw |= !(access.get(i).onPreDraw());
848 }
849 } finally {
850 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 }
852 }
853 return cancelDraw;
854 }
855
856 /**
Romain Guy25eba5c2012-04-04 17:29:03 -0700857 * Notifies registered listeners that the drawing pass is about to start.
858 */
859 public final void dispatchOnDraw() {
860 if (mOnDrawListeners != null) {
861 final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
862 int numListeners = listeners.size();
863 for (int i = 0; i < numListeners; ++i) {
864 listeners.get(i).onDraw();
865 }
866 }
867 }
868
869 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 * Notifies registered listeners that the touch mode has changed.
871 *
872 * @param inTouchMode True if the touch mode is now enabled, false otherwise.
873 */
874 final void dispatchOnTouchModeChanged(boolean inTouchMode) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700875 final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700876 mOnTouchModeChangeListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700877 if (listeners != null && listeners.size() > 0) {
Chet Haase0f8ffd82012-06-07 07:48:48 -0700878 for (OnTouchModeChangeListener listener : listeners) {
879 listener.onTouchModeChanged(inTouchMode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 }
881 }
882 }
883
884 /**
885 * Notifies registered listeners that something has scrolled.
886 */
887 final void dispatchOnScrollChanged() {
The Android Open Source Project10592532009-03-18 17:39:46 -0700888 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
889 // perform the dispatching. The iterator is a safe guard against listeners that
890 // could mutate the list by calling the various add/remove methods. This prevents
891 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700892 final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700893 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700894 CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
895 try {
896 int count = access.size();
897 for (int i = 0; i < count; i++) {
898 access.get(i).onScrollChanged();
899 }
900 } finally {
901 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902 }
903 }
904 }
905
906 /**
907 * Returns whether there are listeners for computing internal insets.
908 */
909 final boolean hasComputeInternalInsetsListeners() {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700910 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700911 mOnComputeInternalInsetsListeners;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 return (listeners != null && listeners.size() > 0);
913 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700914
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 /**
916 * Calls all listeners to compute the current insets.
917 */
918 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700919 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
920 // perform the dispatching. The iterator is a safe guard against listeners that
921 // could mutate the list by calling the various add/remove methods. This prevents
922 // the array from being modified while we iterate it.
Romain Guyc39ed4a2012-06-12 12:06:46 -0700923 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
The Android Open Source Project10592532009-03-18 17:39:46 -0700924 mOnComputeInternalInsetsListeners;
Chet Haase6e0ecb42010-11-03 19:41:18 -0700925 if (listeners != null && listeners.size() > 0) {
Romain Guyc39ed4a2012-06-12 12:06:46 -0700926 CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
927 try {
928 int count = access.size();
929 for (int i = 0; i < count; i++) {
930 access.get(i).onComputeInternalInsets(inoutInfo);
931 }
932 } finally {
933 listeners.end();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 }
935 }
936 }
Romain Guyc39ed4a2012-06-12 12:06:46 -0700937
938 /**
939 * Copy on write array. This array is not thread safe, and only one loop can
940 * iterate over this array at any given time. This class avoids allocations
941 * until a concurrent modification happens.
942 *
943 * Usage:
944 *
945 * CopyOnWriteArray.Access<MyData> access = array.start();
946 * try {
947 * for (int i = 0; i < access.size(); i++) {
948 * MyData d = access.get(i);
949 * }
950 * } finally {
951 * access.end();
952 * }
953 */
954 static class CopyOnWriteArray<T> {
955 private ArrayList<T> mData = new ArrayList<T>();
956 private ArrayList<T> mDataCopy;
957
958 private final Access<T> mAccess = new Access<T>();
959
960 private boolean mStart;
961
962 static class Access<T> {
963 private ArrayList<T> mData;
964 private int mSize;
965
966 T get(int index) {
967 return mData.get(index);
968 }
969
970 int size() {
971 return mSize;
972 }
973 }
974
975 CopyOnWriteArray() {
976 }
977
978 private ArrayList<T> getArray() {
979 if (mStart) {
980 if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
981 return mDataCopy;
982 }
983 return mData;
984 }
985
986 Access<T> start() {
987 if (mStart) throw new IllegalStateException("Iteration already started");
988 mStart = true;
989 mDataCopy = null;
990 mAccess.mData = mData;
991 mAccess.mSize = mData.size();
992 return mAccess;
993 }
994
995 void end() {
996 if (!mStart) throw new IllegalStateException("Iteration not started");
997 mStart = false;
998 if (mDataCopy != null) {
999 mData = mDataCopy;
Chet Haasefc343962013-09-18 08:44:33 -07001000 mAccess.mData.clear();
1001 mAccess.mSize = 0;
Romain Guyc39ed4a2012-06-12 12:06:46 -07001002 }
1003 mDataCopy = null;
1004 }
1005
1006 int size() {
1007 return getArray().size();
1008 }
1009
1010 void add(T item) {
1011 getArray().add(item);
1012 }
1013
1014 void addAll(CopyOnWriteArray<T> array) {
1015 getArray().addAll(array.mData);
1016 }
1017
1018 void remove(T item) {
1019 getArray().remove(item);
1020 }
1021
1022 void clear() {
1023 getArray().clear();
1024 }
1025 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026}