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