blob: 47b52e4a9ad7e4375800137d31fa68789023b33b [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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.ArrayList;
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 ArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
34 private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
35 private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
36 private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
37 private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
38 private ArrayList<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 * @hide pending API council approval
107 */
108 public interface OnScrollChangedListener {
109 /**
110 * Callback method to be invoked when something in the view tree
111 * has been scrolled.
112 */
113 public void onScrollChanged();
114 }
115
116 /**
117 * Parameters used with OnComputeInternalInsetsListener.
118 * {@hide pending API Council approval}
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 * {@hide pending API Council approval}
204 */
205 public interface OnComputeInternalInsetsListener {
206 /**
207 * Callback method to be invoked when layout has completed and the
208 * client can compute its interior insets.
209 *
210 * @param inoutInfo Should be filled in by the implementation with
211 * the information about the insets of the window. This is called
212 * with whatever values the previous OnComputeInternalInsetsListener
213 * returned, if there are multiple such listeners in the window.
214 */
215 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
216 }
217
218 /**
219 * Creates a new ViewTreeObserver. This constructor should not be called
220 */
221 ViewTreeObserver() {
222 }
223
224 /**
225 * Merges all the listeners registered on the specified observer with the listeners
226 * registered on this object. After this method is invoked, the specified observer
227 * will return false in {@link #isAlive()} and should not be used anymore.
228 *
229 * @param observer The ViewTreeObserver whose listeners must be added to this observer
230 */
231 void merge(ViewTreeObserver observer) {
232 if (observer.mOnGlobalFocusListeners != null) {
233 if (mOnGlobalFocusListeners != null) {
234 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
235 } else {
236 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
237 }
238 }
239
240 if (observer.mOnGlobalLayoutListeners != null) {
241 if (mOnGlobalLayoutListeners != null) {
242 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
243 } else {
244 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
245 }
246 }
247
248 if (observer.mOnPreDrawListeners != null) {
249 if (mOnPreDrawListeners != null) {
250 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
251 } else {
252 mOnPreDrawListeners = observer.mOnPreDrawListeners;
253 }
254 }
255
256 if (observer.mOnTouchModeChangeListeners != null) {
257 if (mOnTouchModeChangeListeners != null) {
258 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
259 } else {
260 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
261 }
262 }
263
264 if (observer.mOnComputeInternalInsetsListeners != null) {
265 if (mOnComputeInternalInsetsListeners != null) {
266 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
267 } else {
268 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
269 }
270 }
271
272 observer.kill();
273 }
274
275 /**
276 * Register a callback to be invoked when the focus state within the view tree changes.
277 *
278 * @param listener The callback to add
279 *
280 * @throws IllegalStateException If {@link #isAlive()} returns false
281 */
282 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
283 checkIsAlive();
284
285 if (mOnGlobalFocusListeners == null) {
286 mOnGlobalFocusListeners = new ArrayList<OnGlobalFocusChangeListener>();
287 }
288
289 mOnGlobalFocusListeners.add(listener);
290 }
291
292 /**
293 * Remove a previously installed focus change callback.
294 *
295 * @param victim The callback to remove
296 *
297 * @throws IllegalStateException If {@link #isAlive()} returns false
298 *
299 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
300 */
301 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
302 checkIsAlive();
303 if (mOnGlobalFocusListeners == null) {
304 return;
305 }
306 mOnGlobalFocusListeners.remove(victim);
307 }
308
309 /**
310 * Register a callback to be invoked when the global layout state or the visibility of views
311 * within the view tree changes
312 *
313 * @param listener The callback to add
314 *
315 * @throws IllegalStateException If {@link #isAlive()} returns false
316 */
317 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
318 checkIsAlive();
319
320 if (mOnGlobalLayoutListeners == null) {
321 mOnGlobalLayoutListeners = new ArrayList<OnGlobalLayoutListener>();
322 }
323
324 mOnGlobalLayoutListeners.add(listener);
325 }
326
327 /**
328 * Remove a previously installed global layout callback
329 *
330 * @param victim The callback to remove
331 *
332 * @throws IllegalStateException If {@link #isAlive()} returns false
333 *
334 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
335 */
336 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
337 checkIsAlive();
338 if (mOnGlobalLayoutListeners == null) {
339 return;
340 }
341 mOnGlobalLayoutListeners.remove(victim);
342 }
343
344 /**
345 * Register a callback to be invoked when the view tree is about to be drawn
346 *
347 * @param listener The callback to add
348 *
349 * @throws IllegalStateException If {@link #isAlive()} returns false
350 */
351 public void addOnPreDrawListener(OnPreDrawListener listener) {
352 checkIsAlive();
353
354 if (mOnPreDrawListeners == null) {
355 mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
356 }
357
358 mOnPreDrawListeners.add(listener);
359 }
360
361 /**
362 * Remove a previously installed pre-draw callback
363 *
364 * @param victim The callback to remove
365 *
366 * @throws IllegalStateException If {@link #isAlive()} returns false
367 *
368 * @see #addOnPreDrawListener(OnPreDrawListener)
369 */
370 public void removeOnPreDrawListener(OnPreDrawListener victim) {
371 checkIsAlive();
372 if (mOnPreDrawListeners == null) {
373 return;
374 }
375 mOnPreDrawListeners.remove(victim);
376 }
377
378 /**
379 * Register a callback to be invoked when a view has been scrolled.
380 *
381 * @param listener The callback to add
382 *
383 * @throws IllegalStateException If {@link #isAlive()} returns false
384 *
385 * @hide pending API council approval
386 */
387 public void addOnScrollChangedListener(OnScrollChangedListener listener) {
388 checkIsAlive();
389
390 if (mOnScrollChangedListeners == null) {
391 mOnScrollChangedListeners = new ArrayList<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 * @hide pending API council approval
407 */
408 public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
409 checkIsAlive();
410 if (mOnScrollChangedListeners == null) {
411 return;
412 }
413 mOnScrollChangedListeners.remove(victim);
414 }
415
416 /**
417 * Register a callback to be invoked when the invoked when the touch mode changes.
418 *
419 * @param listener The callback to add
420 *
421 * @throws IllegalStateException If {@link #isAlive()} returns false
422 */
423 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
424 checkIsAlive();
425
426 if (mOnTouchModeChangeListeners == null) {
427 mOnTouchModeChangeListeners = new ArrayList<OnTouchModeChangeListener>();
428 }
429
430 mOnTouchModeChangeListeners.add(listener);
431 }
432
433 /**
434 * Remove a previously installed touch mode change callback
435 *
436 * @param victim The callback to remove
437 *
438 * @throws IllegalStateException If {@link #isAlive()} returns false
439 *
440 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
441 */
442 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
443 checkIsAlive();
444 if (mOnTouchModeChangeListeners == null) {
445 return;
446 }
447 mOnTouchModeChangeListeners.remove(victim);
448 }
449
450 /**
451 * Register a callback to be invoked when the invoked when it is time to
452 * compute the window's internal insets.
453 *
454 * @param listener The callback to add
455 *
456 * @throws IllegalStateException If {@link #isAlive()} returns false
457 * {@hide pending API Council approval}
458 */
459 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
460 checkIsAlive();
461
462 if (mOnComputeInternalInsetsListeners == null) {
463 mOnComputeInternalInsetsListeners = new ArrayList<OnComputeInternalInsetsListener>();
464 }
465
466 mOnComputeInternalInsetsListeners.add(listener);
467 }
468
469 /**
470 * Remove a previously installed internal insets computation callback
471 *
472 * @param victim The callback to remove
473 *
474 * @throws IllegalStateException If {@link #isAlive()} returns false
475 *
476 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
477 * {@hide pending API Council approval}
478 */
479 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
480 checkIsAlive();
481 if (mOnComputeInternalInsetsListeners == null) {
482 return;
483 }
484 mOnComputeInternalInsetsListeners.remove(victim);
485 }
486
487 private void checkIsAlive() {
488 if (!mAlive) {
489 throw new IllegalStateException("This ViewTreeObserver is not alive, call "
490 + "getViewTreeObserver() again");
491 }
492 }
493
494 /**
495 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
496 * any call to a method (except this one) will throw an exception.
497 *
498 * If an application keeps a long-lived reference to this ViewTreeObserver, it should
499 * always check for the result of this method before calling any other method.
500 *
501 * @return True if this object is alive and be used, false otherwise.
502 */
503 public boolean isAlive() {
504 return mAlive;
505 }
506
507 /**
508 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
509 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
510 *
511 * @hide
512 */
513 private void kill() {
514 mAlive = false;
515 }
516
517 /**
518 * Notifies registered listeners that focus has changed.
519 */
520 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
521 final ArrayList<OnGlobalFocusChangeListener> globaFocusListeners = mOnGlobalFocusListeners;
522 if (globaFocusListeners != null) {
523 final int count = globaFocusListeners.size();
524 for (int i = count - 1; i >= 0; i--) {
525 globaFocusListeners.get(i).onGlobalFocusChanged(oldFocus, newFocus);
526 }
527 }
528 }
529
530 /**
531 * Notifies registered listeners that a global layout happened. This can be called
532 * manually if you are forcing a layout on a View or a hierarchy of Views that are
533 * not attached to a Window or in the GONE state.
534 */
535 public final void dispatchOnGlobalLayout() {
536 final ArrayList<OnGlobalLayoutListener> globaLayoutListeners = mOnGlobalLayoutListeners;
537 if (globaLayoutListeners != null) {
538 final int count = globaLayoutListeners.size();
539 for (int i = count - 1; i >= 0; i--) {
540 globaLayoutListeners.get(i).onGlobalLayout();
541 }
542 }
543 }
544
545 /**
546 * Notifies registered listeners that the drawing pass is about to start. If a
547 * listener returns true, then the drawing pass is canceled and rescheduled. This can
548 * be called manually if you are forcing the drawing on a View or a hierarchy of Views
549 * that are not attached to a Window or in the GONE state.
550 *
551 * @return True if the current draw should be canceled and resceduled, false otherwise.
552 */
553 public final boolean dispatchOnPreDraw() {
554 boolean cancelDraw = false;
555 final ArrayList<OnPreDrawListener> preDrawListeners = mOnPreDrawListeners;
556 if (preDrawListeners != null) {
557 final int count = preDrawListeners.size();
558 for (int i = count - 1; i >= 0; i--) {
559 cancelDraw |= !preDrawListeners.get(i).onPreDraw();
560 }
561 }
562 return cancelDraw;
563 }
564
565 /**
566 * Notifies registered listeners that the touch mode has changed.
567 *
568 * @param inTouchMode True if the touch mode is now enabled, false otherwise.
569 */
570 final void dispatchOnTouchModeChanged(boolean inTouchMode) {
571 final ArrayList<OnTouchModeChangeListener> touchModeListeners = mOnTouchModeChangeListeners;
572 if (touchModeListeners != null) {
573 final int count = touchModeListeners.size();
574 for (int i = count - 1; i >= 0; i--) {
575 touchModeListeners.get(i).onTouchModeChanged(inTouchMode);
576 }
577 }
578 }
579
580 /**
581 * Notifies registered listeners that something has scrolled.
582 */
583 final void dispatchOnScrollChanged() {
584 final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
585
586 if (listeners != null) {
587 for (OnScrollChangedListener scl : mOnScrollChangedListeners) {
588 scl.onScrollChanged();
589 }
590 }
591 }
592
593 /**
594 * Returns whether there are listeners for computing internal insets.
595 */
596 final boolean hasComputeInternalInsetsListeners() {
597 final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
598 return (listeners != null && listeners.size() > 0);
599 }
600
601 /**
602 * Calls all listeners to compute the current insets.
603 */
604 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
605 final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
606 if (listeners != null) {
607 final int count = listeners.size();
608 for (int i = count - 1; i >= 0; i--) {
609 listeners.get(i).onComputeInternalInsets(inoutInfo);
610 }
611 }
612 }
613}