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