Home | History | Annotate | Download | only in view
      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          */
     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          */
     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          */
     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          */
    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          */
    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          */
    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          */
    193         public void setTouchableInsets(int val) {
    194             mTouchableInsets = val;
    195         }
    196 
    197         int mTouchableInsets;
    198 
    199         void reset() {
    200             contentInsets.setEmpty();
    201             visibleInsets.setEmpty();
    202             touchableRegion.setEmpty();
    203             mTouchableInsets = TOUCHABLE_INSETS_FRAME;
    204         }
    205 
    206         @Override
    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
    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 
    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          */
    252         public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
    253     }
    254 
    255     /**
    256      * Creates a new ViewTreeObserver. This constructor should not be called
    257      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    579     public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
    580         checkIsAlive();
    581         if (mOnComputeInternalInsetsListeners == null) {
    582             return;
    583         }
    584         mOnComputeInternalInsetsListeners.remove(victim);
    585     }
    586 
    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      */
    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      */
    613     private void kill() {
    614         mAlive = false;
    615     }
    616 
    617     /**
    618      * Notifies registered listeners that focus has changed.
    619      */
    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      */
    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")
    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      */
    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      */
    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      */
    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      */
    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      */
    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