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