ViewTreeObserver.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 java.util.ArrayList; 20 21/** 22 * A view tree observer is used to register listeners that can be notified of global 23 * changes in the view tree. Such global events include, but are not limited to, 24 * layout of the whole tree, beginning of the drawing pass, touch mode change.... 25 * 26 * A ViewTreeObserver should never be instantiated by applications as it is provided 27 * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()} 28 * for more information. 29 */ 30public final class ViewTreeObserver { 31 private ArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; 32 private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; 33 private ArrayList<OnPreDrawListener> mOnPreDrawListeners; 34 private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; 35 36 private boolean mAlive = true; 37 38 /** 39 * Interface definition for a callback to be invoked when the focus state within 40 * the view tree changes. 41 */ 42 public interface OnGlobalFocusChangeListener { 43 /** 44 * Callback method to be invoked when the focus changes in the view tree. When 45 * the view tree transitions from touch mode to non-touch mode, oldFocus is null. 46 * When the view tree transitions from non-touch mode to touch mode, newFocus is 47 * null. When focus changes in non-touch mode (without transition from or to 48 * touch mode) either oldFocus or newFocus can be null. 49 * 50 * @param oldFocus The previously focused view, if any. 51 * @param newFocus The newly focused View, if any. 52 */ 53 public void onGlobalFocusChanged(View oldFocus, View newFocus); 54 } 55 56 /** 57 * Interface definition for a callback to be invoked when the global layout state 58 * or the visibility of views within the view tree changes. 59 */ 60 public interface OnGlobalLayoutListener { 61 /** 62 * Callback method to be invoked when the global layout state or the visibility of views 63 * within the view tree changes 64 */ 65 public void onGlobalLayout(); 66 } 67 68 /** 69 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 70 */ 71 public interface OnPreDrawListener { 72 /** 73 * Callback method to be invoked when the view tree is about to be drawn. At this point, all 74 * views in the tree have been measured and given a frame. Clients can use this to adjust 75 * their scroll bounds or even to request a new layout before drawing occurs. 76 * 77 * @return Return true to proceed with the current drawing pass, or false to cancel. 78 * 79 * @see android.view.View#onMeasure 80 * @see android.view.View#onLayout 81 * @see android.view.View#onDraw 82 */ 83 public boolean onPreDraw(); 84 } 85 86 /** 87 * Interface definition for a callback to be invoked when the touch mode changes. 88 */ 89 public interface OnTouchModeChangeListener { 90 /** 91 * Callback method to be invoked when the touch mode changes. 92 * 93 * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise. 94 */ 95 public void onTouchModeChanged(boolean isInTouchMode); 96 } 97 98 /** 99 * Creates a new ViewTreeObserver. This constructor should not be called 100 */ 101 ViewTreeObserver() { 102 } 103 104 /** 105 * Merges all the listeners registered on the specified observer with the listeners 106 * registered on this object. After this method is invoked, the specified observer 107 * will return false in {@link #isAlive()} and should not be used anymore. 108 * 109 * @param observer The ViewTreeObserver whose listeners must be added to this observer 110 */ 111 void merge(ViewTreeObserver observer) { 112 if (observer.mOnGlobalFocusListeners != null) { 113 if (mOnGlobalFocusListeners != null) { 114 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners); 115 } else { 116 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners; 117 } 118 } 119 120 if (observer.mOnGlobalLayoutListeners != null) { 121 if (mOnGlobalLayoutListeners != null) { 122 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); 123 } else { 124 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners; 125 } 126 } 127 128 if (observer.mOnPreDrawListeners != null) { 129 if (mOnPreDrawListeners != null) { 130 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners); 131 } else { 132 mOnPreDrawListeners = observer.mOnPreDrawListeners; 133 } 134 } 135 136 if (observer.mOnTouchModeChangeListeners != null) { 137 if (mOnTouchModeChangeListeners != null) { 138 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); 139 } else { 140 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners; 141 } 142 } 143 144 observer.kill(); 145 } 146 147 /** 148 * Register a callback to be invoked when the focus state within the view tree changes. 149 * 150 * @param listener The callback to add 151 * 152 * @throws IllegalStateException If {@link #isAlive()} returns false 153 */ 154 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) { 155 checkIsAlive(); 156 157 if (mOnGlobalFocusListeners == null) { 158 mOnGlobalFocusListeners = new ArrayList<OnGlobalFocusChangeListener>(); 159 } 160 161 mOnGlobalFocusListeners.add(listener); 162 } 163 164 /** 165 * Remove a previously installed focus change callback. 166 * 167 * @param victim The callback to remove 168 * 169 * @throws IllegalStateException If {@link #isAlive()} returns false 170 * 171 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener) 172 */ 173 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) { 174 checkIsAlive(); 175 if (mOnGlobalFocusListeners == null) { 176 return; 177 } 178 mOnGlobalFocusListeners.remove(victim); 179 } 180 181 /** 182 * Register a callback to be invoked when the global layout state or the visibility of views 183 * within the view tree changes 184 * 185 * @param listener The callback to add 186 * 187 * @throws IllegalStateException If {@link #isAlive()} returns false 188 */ 189 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) { 190 checkIsAlive(); 191 192 if (mOnGlobalLayoutListeners == null) { 193 mOnGlobalLayoutListeners = new ArrayList<OnGlobalLayoutListener>(); 194 } 195 196 mOnGlobalLayoutListeners.add(listener); 197 } 198 199 /** 200 * Remove a previously installed global layout callback 201 * 202 * @param victim The callback to remove 203 * 204 * @throws IllegalStateException If {@link #isAlive()} returns false 205 * 206 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 207 */ 208 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { 209 checkIsAlive(); 210 if (mOnGlobalLayoutListeners == null) { 211 return; 212 } 213 mOnGlobalLayoutListeners.remove(victim); 214 } 215 216 /** 217 * Register a callback to be invoked when the view tree is about to be drawn 218 * 219 * @param listener The callback to add 220 * 221 * @throws IllegalStateException If {@link #isAlive()} returns false 222 */ 223 public void addOnPreDrawListener(OnPreDrawListener listener) { 224 checkIsAlive(); 225 226 if (mOnPreDrawListeners == null) { 227 mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); 228 } 229 230 mOnPreDrawListeners.add(listener); 231 } 232 233 /** 234 * Remove a previously installed pre-draw callback 235 * 236 * @param victim The callback to remove 237 * 238 * @throws IllegalStateException If {@link #isAlive()} returns false 239 * 240 * @see #addOnPreDrawListener(OnPreDrawListener) 241 */ 242 public void removeOnPreDrawListener(OnPreDrawListener victim) { 243 checkIsAlive(); 244 if (mOnPreDrawListeners == null) { 245 return; 246 } 247 mOnPreDrawListeners.remove(victim); 248 } 249 250 /** 251 * Register a callback to be invoked when the invoked when the touch mode changes. 252 * 253 * @param listener The callback to add 254 * 255 * @throws IllegalStateException If {@link #isAlive()} returns false 256 */ 257 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) { 258 checkIsAlive(); 259 260 if (mOnTouchModeChangeListeners == null) { 261 mOnTouchModeChangeListeners = new ArrayList<OnTouchModeChangeListener>(); 262 } 263 264 mOnTouchModeChangeListeners.add(listener); 265 } 266 267 /** 268 * Remove a previously installed touch mode change callback 269 * 270 * @param victim The callback to remove 271 * 272 * @throws IllegalStateException If {@link #isAlive()} returns false 273 * 274 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener) 275 */ 276 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) { 277 checkIsAlive(); 278 if (mOnTouchModeChangeListeners == null) { 279 return; 280 } 281 mOnTouchModeChangeListeners.remove(victim); 282 } 283 284 private void checkIsAlive() { 285 if (!mAlive) { 286 throw new IllegalStateException("This ViewTreeObserver is not alive, call " 287 + "getViewTreeObserver() again"); 288 } 289 } 290 291 /** 292 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive, 293 * any call to a method (except this one) will throw an exception. 294 * 295 * If an application keeps a long-lived reference to this ViewTreeObserver, it should 296 * always check for the result of this method before calling any other method. 297 * 298 * @return True if this object is alive and be used, false otherwise. 299 */ 300 public boolean isAlive() { 301 return mAlive; 302 } 303 304 /** 305 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking 306 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception. 307 * 308 * @hide 309 */ 310 private void kill() { 311 mAlive = false; 312 } 313 314 /** 315 * Notifies registered listeners that focus has changed. 316 */ 317 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { 318 final ArrayList<OnGlobalFocusChangeListener> globaFocusListeners = mOnGlobalFocusListeners; 319 if (globaFocusListeners != null) { 320 final int count = globaFocusListeners.size(); 321 for (int i = count - 1; i >= 0; i--) { 322 globaFocusListeners.get(i).onGlobalFocusChanged(oldFocus, newFocus); 323 } 324 } 325 } 326 327 /** 328 * Notifies registered listeners that a global layout happened. This can be called 329 * manually if you are forcing a layout on a View or a hierarchy of Views that are 330 * not attached to a Window or in the GONE state. 331 */ 332 public final void dispatchOnGlobalLayout() { 333 final ArrayList<OnGlobalLayoutListener> globaLayoutListeners = mOnGlobalLayoutListeners; 334 if (globaLayoutListeners != null) { 335 final int count = globaLayoutListeners.size(); 336 for (int i = count - 1; i >= 0; i--) { 337 globaLayoutListeners.get(i).onGlobalLayout(); 338 } 339 } 340 } 341 342 /** 343 * Notifies registered listeners that the drawing pass is about to start. If a 344 * listener returns true, then the drawing pass is canceled and rescheduled. This can 345 * be called manually if you are forcing the drawing on a View or a hierarchy of Views 346 * that are not attached to a Window or in the GONE state. 347 * 348 * @return True if the current draw should be canceled and resceduled, false otherwise. 349 */ 350 public final boolean dispatchOnPreDraw() { 351 boolean cancelDraw = false; 352 final ArrayList<OnPreDrawListener> preDrawListeners = mOnPreDrawListeners; 353 if (preDrawListeners != null) { 354 final int count = preDrawListeners.size(); 355 for (int i = count - 1; i >= 0; i--) { 356 cancelDraw |= !preDrawListeners.get(i).onPreDraw(); 357 } 358 } 359 return cancelDraw; 360 } 361 362 /** 363 * Notifies registered listeners that the touch mode has changed. 364 * 365 * @param inTouchMode True if the touch mode is now enabled, false otherwise. 366 */ 367 final void dispatchOnTouchModeChanged(boolean inTouchMode) { 368 final ArrayList<OnTouchModeChangeListener> touchModeListeners = mOnTouchModeChangeListeners; 369 if (touchModeListeners != null) { 370 final int count = touchModeListeners.size(); 371 for (int i = count - 1; i >= 0; i--) { 372 touchModeListeners.get(i).onTouchModeChanged(inTouchMode); 373 } 374 } 375 } 376} 377