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