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