ViewTreeObserver.java revision f8a7ceaef2e7d5cd530c9426bde91b6fa9a40b75
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
17package android.view;
18
19import android.graphics.Rect;
20
21import 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 */
32public 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