• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.graphics.Rect;
20 import android.graphics.Region;
21 
22 import java.util.ArrayList;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 
25 /**
26  * A view tree observer is used to register listeners that can be notified of global
27  * changes in the view tree. Such global events include, but are not limited to,
28  * layout of the whole tree, beginning of the drawing pass, touch mode change....
29  *
30  * A ViewTreeObserver should never be instantiated by applications as it is provided
31  * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
32  * for more information.
33  */
34 public final class ViewTreeObserver {
35     // Recursive listeners use CopyOnWriteArrayList
36     private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
37     private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
38     private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
39     private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
40 
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
49     private ArrayList<OnDrawListener> mOnDrawListeners;
50 
51     private boolean mAlive = true;
52 
53     /**
54      * 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          */
onWindowAttached()61         public void onWindowAttached();
62 
63         /**
64          * Callback method to be invoked when the view hierarchy is detached from a window
65          */
onWindowDetached()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          */
onWindowFocusChanged(boolean hasFocus)80         public void onWindowFocusChanged(boolean hasFocus);
81     }
82 
83     /**
84      * 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          */
onGlobalFocusChanged(View oldFocus, View newFocus)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          */
onGlobalLayout()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          */
onPreDraw()128         public boolean onPreDraw();
129     }
130 
131     /**
132      * 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          */
onDraw()149         public void onDraw();
150     }
151 
152     /**
153      * 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          */
onTouchModeChanged(boolean isInTouchMode)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.
167      */
168     public interface OnScrollChangedListener {
169         /**
170          * Callback method to be invoked when something in the view tree
171          * has been scrolled.
172          */
onScrollChanged()173         public void onScrollChanged();
174     }
175 
176     /**
177      * Parameters used with OnComputeInternalInsetsListener.
178      *
179      * We are not yet ready to commit to this API and support it, so
180      * @hide
181      */
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();
188 
189         /**
190          * Offsets from the frame of the window at which windows behind it
191          * are visible.
192          */
193         public final Rect visibleInsets = new Rect();
194 
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 
202         /**
203          * Option for {@link #setTouchableInsets(int)}: the entire window frame
204          * can be touched.
205          */
206         public static final int TOUCHABLE_INSETS_FRAME = 0;
207 
208         /**
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;
213 
214         /**
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;
219 
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 
226         /**
227          * Set which parts of the window can be touched: either
228          * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
229          * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
230          */
setTouchableInsets(int val)231         public void setTouchableInsets(int val) {
232             mTouchableInsets = val;
233         }
234 
235         int mTouchableInsets;
236 
reset()237         void reset() {
238             contentInsets.setEmpty();
239             visibleInsets.setEmpty();
240             touchableRegion.setEmpty();
241             mTouchableInsets = TOUCHABLE_INSETS_FRAME;
242         }
243 
isEmpty()244         boolean isEmpty() {
245             return contentInsets.isEmpty()
246                     && visibleInsets.isEmpty()
247                     && touchableRegion.isEmpty()
248                     && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
249         }
250 
251         @Override
hashCode()252         public int hashCode() {
253             int result = contentInsets.hashCode();
254             result = 31 * result + visibleInsets.hashCode();
255             result = 31 * result + touchableRegion.hashCode();
256             result = 31 * result + mTouchableInsets;
257             return result;
258         }
259 
260         @Override
equals(Object o)261         public boolean equals(Object o) {
262             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);
270         }
271 
set(InternalInsetsInfo other)272         void set(InternalInsetsInfo other) {
273             contentInsets.set(other.contentInsets);
274             visibleInsets.set(other.visibleInsets);
275             touchableRegion.set(other.touchableRegion);
276             mTouchableInsets = other.mTouchableInsets;
277         }
278     }
279 
280     /**
281      * Interface definition for a callback to be invoked when layout has
282      * completed and the client can compute its interior insets.
283      *
284      * We are not yet ready to commit to this API and support it, so
285      * @hide
286      */
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          */
onComputeInternalInsets(InternalInsetsInfo inoutInfo)297         public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
298     }
299 
300     /**
301      * Creates a new ViewTreeObserver. This constructor should not be called
302      */
ViewTreeObserver()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      */
merge(ViewTreeObserver observer)313     void merge(ViewTreeObserver observer) {
314         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 
330         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 
370         if (observer.mOnScrollChangedListeners != null) {
371             if (mOnScrollChangedListeners != null) {
372                 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
373             } else {
374                 mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
375             }
376         }
377 
378         observer.kill();
379     }
380 
381     /**
382      * 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      */
addOnWindowAttachListener(OnWindowAttachListener listener)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      */
removeOnWindowAttachListener(OnWindowAttachListener victim)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      */
addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener)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      */
removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim)443     public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
444         checkIsAlive();
445         if (mOnWindowFocusListeners == null) {
446             return;
447         }
448         mOnWindowFocusListeners.remove(victim);
449     }
450 
451     /**
452      * 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      */
addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener)458     public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
459         checkIsAlive();
460 
461         if (mOnGlobalFocusListeners == null) {
462             mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
463         }
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      */
removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim)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      */
addOnGlobalLayoutListener(OnGlobalLayoutListener listener)493     public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
494         checkIsAlive();
495 
496         if (mOnGlobalLayoutListeners == null) {
497             mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
498         }
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
509      *
510      * @deprecated Use #removeOnGlobalLayoutListener instead
511      *
512      * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
513      */
514     @Deprecated
removeGlobalOnLayoutListener(OnGlobalLayoutListener victim)515     public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
516         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      */
removeOnGlobalLayoutListener(OnGlobalLayoutListener victim)528     public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
529         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      */
addOnPreDrawListener(OnPreDrawListener listener)543     public void addOnPreDrawListener(OnPreDrawListener listener) {
544         checkIsAlive();
545 
546         if (mOnPreDrawListeners == null) {
547             mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
548         }
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      */
removeOnPreDrawListener(OnPreDrawListener victim)562     public void removeOnPreDrawListener(OnPreDrawListener victim) {
563         checkIsAlive();
564         if (mOnPreDrawListeners == null) {
565             return;
566         }
567         mOnPreDrawListeners.remove(victim);
568     }
569 
570     /**
571      * <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      */
addOnDrawListener(OnDrawListener listener)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      */
removeOnDrawListener(OnDrawListener victim)600     public void removeOnDrawListener(OnDrawListener victim) {
601         checkIsAlive();
602         if (mOnDrawListeners == null) {
603             return;
604         }
605         mOnDrawListeners.remove(victim);
606     }
607 
608     /**
609      * 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
614      */
addOnScrollChangedListener(OnScrollChangedListener listener)615     public void addOnScrollChangedListener(OnScrollChangedListener listener) {
616         checkIsAlive();
617 
618         if (mOnScrollChangedListeners == null) {
619             mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
620         }
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)
633      */
removeOnScrollChangedListener(OnScrollChangedListener victim)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      */
addOnTouchModeChangeListener(OnTouchModeChangeListener listener)649     public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
650         checkIsAlive();
651 
652         if (mOnTouchModeChangeListeners == null) {
653             mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
654         }
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      */
removeOnTouchModeChangeListener(OnTouchModeChangeListener victim)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
683      *
684      * We are not yet ready to commit to this API and support it, so
685      * @hide
686      */
addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener)687     public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
688         checkIsAlive();
689 
690         if (mOnComputeInternalInsetsListeners == null) {
691             mOnComputeInternalInsetsListeners =
692                     new CopyOnWriteArray<OnComputeInternalInsetsListener>();
693         }
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)
706      *
707      * We are not yet ready to commit to this API and support it, so
708      * @hide
709      */
removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim)710     public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
711         checkIsAlive();
712         if (mOnComputeInternalInsetsListeners == null) {
713             return;
714         }
715         mOnComputeInternalInsetsListeners.remove(victim);
716     }
717 
checkIsAlive()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      */
isAlive()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      */
kill()744     private void kill() {
745         mAlive = false;
746     }
747 
748     /**
749      * Notifies registered listeners that window has been attached/detached.
750      */
dispatchOnWindowAttachedChange(boolean attached)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      */
dispatchOnWindowFocusChange(boolean hasFocus)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     /**
784      * Notifies registered listeners that focus has changed.
785      */
dispatchOnGlobalFocusChange(View oldFocus, View newFocus)786     final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
787         // 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.
791         final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
792         if (listeners != null && listeners.size() > 0) {
793             for (OnGlobalFocusChangeListener listener : listeners) {
794                 listener.onGlobalFocusChanged(oldFocus, newFocus);
795             }
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      */
dispatchOnGlobalLayout()804     public final void dispatchOnGlobalLayout() {
805         // 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.
809         final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
810         if (listeners != null && listeners.size() > 0) {
811             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();
819             }
820         }
821     }
822 
823     /**
824      * Returns whether there are listeners for on pre-draw events.
825      */
hasOnPreDrawListeners()826     final boolean hasOnPreDrawListeners() {
827         return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
828     }
829 
830     /**
831      * 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      */
838     @SuppressWarnings("unchecked")
dispatchOnPreDraw()839     public final boolean dispatchOnPreDraw() {
840         boolean cancelDraw = false;
841         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();
851             }
852         }
853         return cancelDraw;
854     }
855 
856     /**
857      * Notifies registered listeners that the drawing pass is about to start.
858      */
dispatchOnDraw()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     /**
870      * Notifies registered listeners that the touch mode has changed.
871      *
872      * @param inTouchMode True if the touch mode is now enabled, false otherwise.
873      */
dispatchOnTouchModeChanged(boolean inTouchMode)874     final void dispatchOnTouchModeChanged(boolean inTouchMode) {
875         final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
876                 mOnTouchModeChangeListeners;
877         if (listeners != null && listeners.size() > 0) {
878             for (OnTouchModeChangeListener listener : listeners) {
879                 listener.onTouchModeChanged(inTouchMode);
880             }
881         }
882     }
883 
884     /**
885      * Notifies registered listeners that something has scrolled.
886      */
dispatchOnScrollChanged()887     final void dispatchOnScrollChanged() {
888         // 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.
892         final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
893         if (listeners != null && listeners.size() > 0) {
894             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();
902             }
903         }
904     }
905 
906     /**
907      * Returns whether there are listeners for computing internal insets.
908      */
hasComputeInternalInsetsListeners()909     final boolean hasComputeInternalInsetsListeners() {
910         final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
911                 mOnComputeInternalInsetsListeners;
912         return (listeners != null && listeners.size() > 0);
913     }
914 
915     /**
916      * Calls all listeners to compute the current insets.
917      */
dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo)918     final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
919         // 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.
923         final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
924                 mOnComputeInternalInsetsListeners;
925         if (listeners != null && listeners.size() > 0) {
926             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();
934             }
935         }
936     }
937 
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 
get(int index)966             T get(int index) {
967                 return mData.get(index);
968             }
969 
size()970             int size() {
971                 return mSize;
972             }
973         }
974 
CopyOnWriteArray()975         CopyOnWriteArray() {
976         }
977 
getArray()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 
start()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 
end()995         void end() {
996             if (!mStart) throw new IllegalStateException("Iteration not started");
997             mStart = false;
998             if (mDataCopy != null) {
999                 mData = mDataCopy;
1000                 mAccess.mData.clear();
1001                 mAccess.mSize = 0;
1002             }
1003             mDataCopy = null;
1004         }
1005 
size()1006         int size() {
1007             return getArray().size();
1008         }
1009 
add(T item)1010         void add(T item) {
1011             getArray().add(item);
1012         }
1013 
addAll(CopyOnWriteArray<T> array)1014         void addAll(CopyOnWriteArray<T> array) {
1015             getArray().addAll(array.mData);
1016         }
1017 
remove(T item)1018         void remove(T item) {
1019             getArray().remove(item);
1020         }
1021 
clear()1022         void clear() {
1023             getArray().clear();
1024         }
1025     }
1026 }
1027