ViewCompat.java revision ec03704fbb4f0217d4c274d1c6cf56e6ea4dcfbd
1/* 2 * Copyright (C) 2011 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.support.v4.view; 18 19import android.graphics.Rect; 20import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 21import android.view.View; 22import android.view.accessibility.AccessibilityEvent; 23 24/** 25 * Helper for accessing features in {@link View} introduced after API 26 * level 4 in a backwards compatible fashion. 27 */ 28public class ViewCompat { 29 /** 30 * Always allow a user to over-scroll this view, provided it is a 31 * view that can scroll. 32 */ 33 public static final int OVER_SCROLL_ALWAYS = 0; 34 35 /** 36 * Allow a user to over-scroll this view only if the content is large 37 * enough to meaningfully scroll, provided it is a view that can scroll. 38 */ 39 public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; 40 41 /** 42 * Never allow a user to over-scroll this view. 43 */ 44 public static final int OVER_SCROLL_NEVER = 2; 45 46 private static final long FAKE_FRAME_TIME = 10; 47 48 interface ViewCompatImpl { 49 public boolean canScrollHorizontally(View v, int direction); 50 public boolean canScrollVertically(View v, int direction); 51 public int getOverScrollMode(View v); 52 public void setOverScrollMode(View v, int mode); 53 public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event); 54 public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event); 55 public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info); 56 public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate); 57 public boolean hasTransientState(View view); 58 public void setHasTransientState(View view, boolean hasTransientState); 59 public void postInvalidateOnAnimation(View view); 60 public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom); 61 public void postOnAnimation(View view, Runnable action); 62 public void postOnAnimationDelayed(View view, Runnable action, long delayMillis); 63 } 64 65 static class BaseViewCompatImpl implements ViewCompatImpl { 66 public boolean canScrollHorizontally(View v, int direction) { 67 return false; 68 } 69 public boolean canScrollVertically(View v, int direction) { 70 return false; 71 } 72 public int getOverScrollMode(View v) { 73 return OVER_SCROLL_NEVER; 74 } 75 public void setOverScrollMode(View v, int mode) { 76 // Do nothing; API doesn't exist 77 } 78 public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { 79 // Do nothing; API doesn't exist 80 } 81 public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { 82 // Do nothing; API doesn't exist 83 } 84 public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { 85 // Do nothing; API doesn't exist 86 } 87 public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { 88 // Do nothing; API doesn't exist 89 } 90 public boolean hasTransientState(View view) { 91 // A view can't have transient state if transient state wasn't supported. 92 return false; 93 } 94 public void setHasTransientState(View view, boolean hasTransientState) { 95 // Do nothing; API doesn't exist 96 } 97 public void postInvalidateOnAnimation(View view) { 98 view.postInvalidateDelayed(getFrameTime()); 99 } 100 public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) { 101 view.postInvalidateDelayed(getFrameTime(), left, top, right, bottom); 102 } 103 public void postOnAnimation(View view, Runnable action) { 104 view.postDelayed(action, getFrameTime()); 105 } 106 public void postOnAnimationDelayed(View view, Runnable action, long delayMillis) { 107 view.postDelayed(action, getFrameTime() + delayMillis); 108 } 109 long getFrameTime() { 110 return FAKE_FRAME_TIME; 111 } 112 } 113 114 static class GBViewCompatImpl extends BaseViewCompatImpl { 115 @Override 116 public int getOverScrollMode(View v) { 117 return ViewCompatGingerbread.getOverScrollMode(v); 118 } 119 @Override 120 public void setOverScrollMode(View v, int mode) { 121 ViewCompatGingerbread.setOverScrollMode(v, mode); 122 } 123 } 124 125 static class HCViewCompatImpl extends GBViewCompatImpl { 126 long getFrameTime() { 127 return ViewCompatHC.getFrameTime(); 128 } 129 } 130 131 static class ICSViewCompatImpl extends HCViewCompatImpl { 132 @Override 133 public boolean canScrollHorizontally(View v, int direction) { 134 return ViewCompatICS.canScrollHorizontally(v, direction); 135 } 136 @Override 137 public boolean canScrollVertically(View v, int direction) { 138 return ViewCompatICS.canScrollVertically(v, direction); 139 } 140 @Override 141 public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { 142 ViewCompatICS.onPopulateAccessibilityEvent(v, event); 143 } 144 @Override 145 public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { 146 ViewCompatICS.onInitializeAccessibilityEvent(v, event); 147 } 148 @Override 149 public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { 150 ViewCompatICS.onInitializeAccessibilityNodeInfo(v, info.getInfo()); 151 } 152 @Override 153 public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { 154 ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge()); 155 } 156 } 157 158 static class JBViewCompatImpl extends ICSViewCompatImpl { 159 @Override 160 public boolean hasTransientState(View view) { 161 return ViewCompatJB.hasTransientState(view); 162 } 163 @Override 164 public void setHasTransientState(View view, boolean hasTransientState) { 165 ViewCompatJB.setHasTransientState(view, hasTransientState); 166 } 167 @Override 168 public void postInvalidateOnAnimation(View view) { 169 ViewCompatJB.postInvalidateOnAnimation(view); 170 } 171 @Override 172 public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) { 173 ViewCompatJB.postInvalidateOnAnimation(view, left, top, right, bottom); 174 } 175 @Override 176 public void postOnAnimation(View view, Runnable action) { 177 ViewCompatJB.postOnAnimation(view, action); 178 } 179 @Override 180 public void postOnAnimationDelayed(View view, Runnable action, long delayMillis) { 181 ViewCompatJB.postOnAnimationDelayed(view, action, delayMillis); 182 } 183 } 184 185 static final ViewCompatImpl IMPL; 186 static { 187 final int version = android.os.Build.VERSION.SDK_INT; 188 if (version >= 16 || android.os.Build.VERSION.CODENAME.equals("JellyBean")) { 189 IMPL = new JBViewCompatImpl(); 190 } else if (version >= 14) { 191 IMPL = new ICSViewCompatImpl(); 192 } else if (version >= 11) { 193 IMPL = new HCViewCompatImpl(); 194 } else if (version >= 9) { 195 IMPL = new GBViewCompatImpl(); 196 } else { 197 IMPL = new BaseViewCompatImpl(); 198 } 199 } 200 201 /** 202 * Check if this view can be scrolled horizontally in a certain direction. 203 * 204 * @param v The View against which to invoke the method. 205 * @param direction Negative to check scrolling left, positive to check scrolling right. 206 * @return true if this view can be scrolled in the specified direction, false otherwise. 207 */ 208 public static boolean canScrollHorizontally(View v, int direction) { 209 return IMPL.canScrollHorizontally(v, direction); 210 } 211 212 /** 213 * Check if this view can be scrolled vertically in a certain direction. 214 * 215 * @param v The View against which to invoke the method. 216 * @param direction Negative to check scrolling up, positive to check scrolling down. 217 * @return true if this view can be scrolled in the specified direction, false otherwise. 218 */ 219 public static boolean canScrollVertically(View v, int direction) { 220 return IMPL.canScrollVertically(v, direction); 221 } 222 223 /** 224 * Returns the over-scroll mode for this view. The result will be 225 * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} 226 * (allow over-scrolling only if the view content is larger than the container), 227 * or {@link #OVER_SCROLL_NEVER}. 228 * 229 * @param v The View against which to invoke the method. 230 * @return This view's over-scroll mode. 231 */ 232 public static int getOverScrollMode(View v) { 233 return IMPL.getOverScrollMode(v); 234 } 235 236 /** 237 * Set the over-scroll mode for this view. Valid over-scroll modes are 238 * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} 239 * (allow over-scrolling only if the view content is larger than the container), 240 * or {@link #OVER_SCROLL_NEVER}. 241 * 242 * Setting the over-scroll mode of a view will have an effect only if the 243 * view is capable of scrolling. 244 * 245 * @param v The View against which to invoke the method. 246 * @param overScrollMode The new over-scroll mode for this view. 247 */ 248 public static void setOverScrollMode(View v, int overScrollMode) { 249 IMPL.setOverScrollMode(v, overScrollMode); 250 } 251 252 /** 253 * Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} 254 * giving a chance to this View to populate the accessibility event with its 255 * text content. While this method is free to modify event 256 * attributes other than text content, doing so should normally be performed in 257 * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}. 258 * <p> 259 * Example: Adding formatted date string to an accessibility event in addition 260 * to the text added by the super implementation: 261 * <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 262 * super.onPopulateAccessibilityEvent(event); 263 * final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY; 264 * String selectedDateUtterance = DateUtils.formatDateTime(mContext, 265 * mCurrentDate.getTimeInMillis(), flags); 266 * event.getText().add(selectedDateUtterance); 267 * }</pre> 268 * <p> 269 * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling 270 * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its 271 * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View, 272 * AccessibilityEvent)} 273 * is responsible for handling this call. 274 * </p> 275 * <p class="note"><strong>Note:</strong> Always call the super implementation before adding 276 * information to the event, in case the default implementation has basic information to add. 277 * </p> 278 * 279 * @param v The View against which to invoke the method. 280 * @param event The accessibility event which to populate. 281 * 282 * @see View#sendAccessibilityEvent(int) 283 * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) 284 */ 285 public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { 286 IMPL.onPopulateAccessibilityEvent(v, event); 287 } 288 289 /** 290 * Initializes an {@link AccessibilityEvent} with information about 291 * this View which is the event source. In other words, the source of 292 * an accessibility event is the view whose state change triggered firing 293 * the event. 294 * <p> 295 * Example: Setting the password property of an event in addition 296 * to properties set by the super implementation: 297 * <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 298 * super.onInitializeAccessibilityEvent(event); 299 * event.setPassword(true); 300 * }</pre> 301 * <p> 302 * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling 303 * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its 304 * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View, 305 * AccessibilityEvent)} 306 * is responsible for handling this call. 307 * </p> 308 * <p class="note"><strong>Note:</strong> Always call the super implementation before adding 309 * information to the event, in case the default implementation has basic information to add. 310 * </p> 311 * 312 * @param v The View against which to invoke the method. 313 * @param event The event to initialize. 314 * 315 * @see View#sendAccessibilityEvent(int) 316 * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) 317 */ 318 public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { 319 IMPL.onInitializeAccessibilityEvent(v, event); 320 } 321 322 /** 323 * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information 324 * about this view. The base implementation sets: 325 * <ul> 326 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li> 327 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li> 328 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li> 329 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li> 330 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li> 331 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li> 332 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li> 333 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li> 334 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li> 335 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li> 336 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li> 337 * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li> 338 * </ul> 339 * <p> 340 * Subclasses should override this method, call the super implementation, 341 * and set additional attributes. 342 * </p> 343 * <p> 344 * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling 345 * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its 346 * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, 347 * android.view.accessibility.AccessibilityNodeInfo)} 348 * is responsible for handling this call. 349 * </p> 350 * 351 * @param v The View against which to invoke the method. 352 * @param info The instance to initialize. 353 */ 354 public static void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { 355 IMPL.onInitializeAccessibilityNodeInfo(v, info); 356 } 357 358 /** 359 * Sets a delegate for implementing accessibility support via compositon as 360 * opposed to inheritance. The delegate's primary use is for implementing 361 * backwards compatible widgets. For more details see 362 * {@link android.view.View.AccessibilityDelegate}. 363 * 364 * @param v The View against which to invoke the method. 365 * @param delegate The delegate instance. 366 * 367 * @see android.view.View.AccessibilityDelegate 368 */ 369 public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { 370 IMPL.setAccessibilityDelegate(v, delegate); 371 } 372 373 /** 374 * Indicates whether the view is currently tracking transient state that the 375 * app should not need to concern itself with saving and restoring, but that 376 * the framework should take special note to preserve when possible. 377 * 378 * @param view View to check for transient state 379 * @return true if the view has transient state 380 */ 381 public static boolean hasTransientState(View view) { 382 return IMPL.hasTransientState(view); 383 } 384 385 /** 386 * Set whether this view is currently tracking transient state that the 387 * framework should attempt to preserve when possible. 388 * 389 * @param view View tracking transient state 390 * @param hasTransientState true if this view has transient state 391 */ 392 public static void setHasTransientState(View view, boolean hasTransientState) { 393 IMPL.setHasTransientState(view, hasTransientState); 394 } 395 396 /** 397 * <p>Cause an invalidate to happen on the next animation time step, typically the 398 * next display frame.</p> 399 * 400 * <p>This method can be invoked from outside of the UI thread 401 * only when this View is attached to a window.</p> 402 * 403 * @param view View to invalidate 404 */ 405 public static void postInvalidateOnAnimation(View view) { 406 IMPL.postInvalidateOnAnimation(view); 407 } 408 409 /** 410 * <p>Cause an invalidate of the specified area to happen on the next animation 411 * time step, typically the next display frame.</p> 412 * 413 * <p>This method can be invoked from outside of the UI thread 414 * only when this View is attached to a window.</p> 415 * 416 * @param view View to invalidate 417 * @param left The left coordinate of the rectangle to invalidate. 418 * @param top The top coordinate of the rectangle to invalidate. 419 * @param right The right coordinate of the rectangle to invalidate. 420 * @param bottom The bottom coordinate of the rectangle to invalidate. 421 */ 422 public static void postInvalidateOnAnimation(View view, int left, int top, 423 int right, int bottom) { 424 IMPL.postInvalidateOnAnimation(view, left, top, right, bottom); 425 } 426 427 /** 428 * <p>Causes the Runnable to execute on the next animation time step. 429 * The runnable will be run on the user interface thread.</p> 430 * 431 * <p>This method can be invoked from outside of the UI thread 432 * only when this View is attached to a window.</p> 433 * 434 * @param view View to post this Runnable to 435 * @param action The Runnable that will be executed. 436 */ 437 public static void postOnAnimation(View view, Runnable action) { 438 IMPL.postOnAnimation(view, action); 439 } 440 441 /** 442 * <p>Causes the Runnable to execute on the next animation time step, 443 * after the specified amount of time elapses. 444 * The runnable will be run on the user interface thread.</p> 445 * 446 * <p>This method can be invoked from outside of the UI thread 447 * only when this View is attached to a window.</p> 448 * 449 * @param view The view to post this Runnable to 450 * @param action The Runnable that will be executed. 451 * @param delayMillis The delay (in milliseconds) until the Runnable 452 * will be executed. 453 */ 454 public static void postOnAnimationDelayed(View view, Runnable action, long delayMillis) { 455 IMPL.postOnAnimationDelayed(view, action, delayMillis); 456 } 457} 458