1/* 2 * Copyright (C) 2013 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.os.Build; 20import android.support.annotation.RequiresApi; 21import android.util.Log; 22import android.view.MotionEvent; 23import android.view.VelocityTracker; 24import android.view.View; 25import android.view.ViewConfiguration; 26import android.view.ViewParent; 27import android.view.accessibility.AccessibilityEvent; 28 29/** 30 * Helper for accessing features in {@link ViewParent} 31 * introduced after API level 4 in a backwards compatible fashion. 32 */ 33public final class ViewParentCompat { 34 35 private static final String TAG = "ViewParentCompat"; 36 37 static class ViewParentCompatBaseImpl { 38 public boolean onStartNestedScroll(ViewParent parent, View child, View target, 39 int nestedScrollAxes) { 40 if (parent instanceof NestedScrollingParent) { 41 return ((NestedScrollingParent) parent).onStartNestedScroll(child, target, 42 nestedScrollAxes); 43 } 44 return false; 45 } 46 47 public void onNestedScrollAccepted(ViewParent parent, View child, View target, 48 int nestedScrollAxes) { 49 if (parent instanceof NestedScrollingParent) { 50 ((NestedScrollingParent) parent).onNestedScrollAccepted(child, target, 51 nestedScrollAxes); 52 } 53 } 54 55 public void onStopNestedScroll(ViewParent parent, View target) { 56 if (parent instanceof NestedScrollingParent) { 57 ((NestedScrollingParent) parent).onStopNestedScroll(target); 58 } 59 } 60 61 public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed, 62 int dxUnconsumed, int dyUnconsumed) { 63 if (parent instanceof NestedScrollingParent) { 64 ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed, 65 dxUnconsumed, dyUnconsumed); 66 } 67 } 68 69 public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, 70 int[] consumed) { 71 if (parent instanceof NestedScrollingParent) { 72 ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed); 73 } 74 } 75 76 public boolean onNestedFling(ViewParent parent, View target, float velocityX, 77 float velocityY, boolean consumed) { 78 if (parent instanceof NestedScrollingParent) { 79 return ((NestedScrollingParent) parent).onNestedFling(target, velocityX, velocityY, 80 consumed); 81 } 82 return false; 83 } 84 85 public boolean onNestedPreFling(ViewParent parent, View target, float velocityX, 86 float velocityY) { 87 if (parent instanceof NestedScrollingParent) { 88 return ((NestedScrollingParent) parent).onNestedPreFling(target, velocityX, 89 velocityY); 90 } 91 return false; 92 } 93 94 public void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child, 95 View source, int changeType) { 96 } 97 } 98 99 @RequiresApi(19) 100 static class ViewParentCompatApi19Impl extends ViewParentCompatBaseImpl { 101 102 @Override 103 public void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child, 104 View source, int changeType) { 105 parent.notifySubtreeAccessibilityStateChanged(child, source, changeType); 106 } 107 } 108 109 @RequiresApi(21) 110 static class ViewParentCompatApi21Impl extends ViewParentCompatApi19Impl { 111 @Override 112 public boolean onStartNestedScroll(ViewParent parent, View child, View target, 113 int nestedScrollAxes) { 114 try { 115 return parent.onStartNestedScroll(child, target, nestedScrollAxes); 116 } catch (AbstractMethodError e) { 117 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 118 + "method onStartNestedScroll", e); 119 return false; 120 } 121 } 122 123 @Override 124 public void onNestedScrollAccepted(ViewParent parent, View child, View target, 125 int nestedScrollAxes) { 126 try { 127 parent.onNestedScrollAccepted(child, target, nestedScrollAxes); 128 } catch (AbstractMethodError e) { 129 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 130 + "method onNestedScrollAccepted", e); 131 } 132 } 133 134 @Override 135 public void onStopNestedScroll(ViewParent parent, View target) { 136 try { 137 parent.onStopNestedScroll(target); 138 } catch (AbstractMethodError e) { 139 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 140 + "method onStopNestedScroll", e); 141 } 142 } 143 144 @Override 145 public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed, 146 int dxUnconsumed, int dyUnconsumed) { 147 try { 148 parent.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 149 } catch (AbstractMethodError e) { 150 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 151 + "method onNestedScroll", e); 152 } 153 } 154 155 @Override 156 public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, 157 int[] consumed) { 158 try { 159 parent.onNestedPreScroll(target, dx, dy, consumed); 160 } catch (AbstractMethodError e) { 161 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 162 + "method onNestedPreScroll", e); 163 } 164 } 165 166 @Override 167 public boolean onNestedFling(ViewParent parent, View target, float velocityX, 168 float velocityY, boolean consumed) { 169 try { 170 return parent.onNestedFling(target, velocityX, velocityY, consumed); 171 } catch (AbstractMethodError e) { 172 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 173 + "method onNestedFling", e); 174 return false; 175 } 176 } 177 178 @Override 179 public boolean onNestedPreFling(ViewParent parent, View target, float velocityX, 180 float velocityY) { 181 try { 182 return parent.onNestedPreFling(target, velocityX, velocityY); 183 } catch (AbstractMethodError e) { 184 Log.e(TAG, "ViewParent " + parent + " does not implement interface " 185 + "method onNestedPreFling", e); 186 return false; 187 } 188 } 189 } 190 191 static final ViewParentCompatBaseImpl IMPL; 192 static { 193 if (Build.VERSION.SDK_INT >= 21) { 194 IMPL = new ViewParentCompatApi21Impl(); 195 } else if (Build.VERSION.SDK_INT >= 19) { 196 IMPL = new ViewParentCompatApi19Impl(); 197 } else { 198 IMPL = new ViewParentCompatBaseImpl(); 199 } 200 } 201 202 /* 203 * Hide the constructor. 204 */ 205 private ViewParentCompat() {} 206 207 /** 208 * Called by a child to request from its parent to send an {@link AccessibilityEvent}. 209 * The child has already populated a record for itself in the event and is delegating 210 * to its parent to send the event. The parent can optionally add a record for itself. 211 * <p> 212 * Note: An accessibility event is fired by an individual view which populates the 213 * event with a record for its state and requests from its parent to perform 214 * the sending. The parent can optionally add a record for itself before 215 * dispatching the request to its parent. A parent can also choose not to 216 * respect the request for sending the event. The accessibility event is sent 217 * by the topmost view in the view tree.</p> 218 * 219 * @param parent The parent whose method to invoke. 220 * @param child The child which requests sending the event. 221 * @param event The event to be sent. 222 * @return True if the event was sent. 223 * 224 * @deprecated Use {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} 225 * directly. 226 */ 227 @Deprecated 228 public static boolean requestSendAccessibilityEvent( 229 ViewParent parent, View child, AccessibilityEvent event) { 230 return parent.requestSendAccessibilityEvent(child, event); 231 } 232 233 /** 234 * React to a descendant view initiating a nestable scroll operation, claiming the 235 * nested scroll operation if appropriate. 236 * 237 * <p>This version of the method just calls 238 * {@link #onStartNestedScroll(ViewParent, View, View, int, int)} using the touch input type. 239 * </p> 240 * 241 * @param child Direct child of this ViewParent containing target 242 * @param target View that initiated the nested scroll 243 * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, 244 * {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both 245 * @return true if this ViewParent accepts the nested scroll operation 246 */ 247 public static boolean onStartNestedScroll(ViewParent parent, View child, View target, 248 int nestedScrollAxes) { 249 return onStartNestedScroll(parent, child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); 250 } 251 252 /** 253 * React to the successful claiming of a nested scroll operation. 254 * 255 * <p>This version of the method just calls 256 * {@link #onNestedScrollAccepted(ViewParent, View, View, int, int)} using the touch input type. 257 * </p> 258 * 259 * @param child Direct child of this ViewParent containing target 260 * @param target View that initiated the nested scroll 261 * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, 262 * {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both 263 */ 264 public static void onNestedScrollAccepted(ViewParent parent, View child, View target, 265 int nestedScrollAxes) { 266 onNestedScrollAccepted(parent, child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH); 267 } 268 269 /** 270 * React to a nested scroll operation ending. 271 * 272 * <p>This version of the method just calls {@link #onStopNestedScroll(ViewParent, View)} 273 * using the touch input type.</p> 274 * 275 * @param target View that initiated the nested scroll 276 */ 277 public static void onStopNestedScroll(ViewParent parent, View target) { 278 onStopNestedScroll(parent, target, ViewCompat.TYPE_TOUCH); 279 } 280 281 /** 282 * React to a nested scroll in progress. 283 * 284 * <p>This version of the method just calls 285 * {@link #onNestedScroll(ViewParent, View, int, int, int, int, int)} using the touch input 286 * type.</p> 287 * 288 * @param target The descendent view controlling the nested scroll 289 * @param dxConsumed Horizontal scroll distance in pixels already consumed by target 290 * @param dyConsumed Vertical scroll distance in pixels already consumed by target 291 * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target 292 * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target 293 */ 294 public static void onNestedScroll(ViewParent parent, View target, int dxConsumed, 295 int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 296 onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 297 ViewCompat.TYPE_TOUCH); 298 } 299 300 /** 301 * React to a nested scroll in progress before the target view consumes a portion of the scroll. 302 * 303 * <p>This version of the method just calls 304 * {@link #onNestedPreScroll(ViewParent, View, int, int, int[], int)} using the touch input 305 * type.</p> 306 * 307 * @param target View that initiated the nested scroll 308 * @param dx Horizontal scroll distance in pixels 309 * @param dy Vertical scroll distance in pixels 310 * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent 311 */ 312 public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, 313 int[] consumed) { 314 onNestedPreScroll(parent, target, dx, dy, consumed, ViewCompat.TYPE_TOUCH); 315 } 316 317 /** 318 * React to a descendant view initiating a nestable scroll operation, claiming the 319 * nested scroll operation if appropriate. 320 * 321 * <p>This method will be called in response to a descendant view invoking 322 * {@link ViewCompat#startNestedScroll(View, int)}. Each parent up the view hierarchy will be 323 * given an opportunity to respond and claim the nested scrolling operation by returning 324 * <code>true</code>.</p> 325 * 326 * <p>This method may be overridden by ViewParent implementations to indicate when the view 327 * is willing to support a nested scrolling operation that is about to begin. If it returns 328 * true, this ViewParent will become the target view's nested scrolling parent for the duration 329 * of the scroll operation in progress. When the nested scroll is finished this ViewParent 330 * will receive a call to {@link #onStopNestedScroll(ViewParent, View, int)}. 331 * </p> 332 * 333 * @param child Direct child of this ViewParent containing target 334 * @param target View that initiated the nested scroll 335 * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, 336 * {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both 337 * @param type the type of input which cause this scroll event 338 * @return true if this ViewParent accepts the nested scroll operation 339 */ 340 public static boolean onStartNestedScroll(ViewParent parent, View child, View target, 341 int nestedScrollAxes, int type) { 342 if (parent instanceof NestedScrollingParent2) { 343 // First try the NestedScrollingParent2 API 344 return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target, 345 nestedScrollAxes, type); 346 } else if (type == ViewCompat.TYPE_TOUCH) { 347 // Else if the type is the default (touch), try the NestedScrollingParent API 348 return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes); 349 } 350 return false; 351 } 352 353 /** 354 * React to the successful claiming of a nested scroll operation. 355 * 356 * <p>This method will be called after 357 * {@link #onStartNestedScroll(ViewParent, View, View, int) onStartNestedScroll} returns true. 358 * It offers an opportunity for the view and its superclasses to perform initial configuration 359 * for the nested scroll. Implementations of this method should always call their superclass's 360 * implementation of this method if one is present.</p> 361 * 362 * @param child Direct child of this ViewParent containing target 363 * @param target View that initiated the nested scroll 364 * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, 365 * {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both 366 * @param type the type of input which cause this scroll event 367 * @see #onStartNestedScroll(ViewParent, View, View, int) 368 * @see #onStopNestedScroll(ViewParent, View, int) 369 */ 370 public static void onNestedScrollAccepted(ViewParent parent, View child, View target, 371 int nestedScrollAxes, int type) { 372 if (parent instanceof NestedScrollingParent2) { 373 // First try the NestedScrollingParent2 API 374 ((NestedScrollingParent2) parent).onNestedScrollAccepted(child, target, 375 nestedScrollAxes, type); 376 } else if (type == ViewCompat.TYPE_TOUCH) { 377 // Else if the type is the default (touch), try the NestedScrollingParent API 378 IMPL.onNestedScrollAccepted(parent, child, target, nestedScrollAxes); 379 } 380 } 381 382 /** 383 * React to a nested scroll operation ending. 384 * 385 * <p>Perform cleanup after a nested scrolling operation. 386 * This method will be called when a nested scroll stops, for example when a nested touch 387 * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event. 388 * Implementations of this method should always call their superclass's implementation of this 389 * method if one is present.</p> 390 * 391 * @param target View that initiated the nested scroll 392 * @param type the type of input which cause this scroll event 393 */ 394 public static void onStopNestedScroll(ViewParent parent, View target, int type) { 395 if (parent instanceof NestedScrollingParent2) { 396 // First try the NestedScrollingParent2 API 397 ((NestedScrollingParent2) parent).onStopNestedScroll(target, type); 398 } else if (type == ViewCompat.TYPE_TOUCH) { 399 // Else if the type is the default (touch), try the NestedScrollingParent API 400 IMPL.onStopNestedScroll(parent, target); 401 } 402 } 403 404 /** 405 * React to a nested scroll in progress. 406 * 407 * <p>This method will be called when the ViewParent's current nested scrolling child view 408 * dispatches a nested scroll event. To receive calls to this method the ViewParent must have 409 * previously returned <code>true</code> for a call to 410 * {@link #onStartNestedScroll(ViewParent, View, View, int, int)}.</p> 411 * 412 * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the 413 * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll 414 * position of multiple child elements, for example. The unconsumed portion may be used to 415 * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling 416 * a list within a vertical drawer where the drawer begins dragging once the edge of inner 417 * scrolling content is reached.</p> 418 * 419 * @param target The descendent view controlling the nested scroll 420 * @param dxConsumed Horizontal scroll distance in pixels already consumed by target 421 * @param dyConsumed Vertical scroll distance in pixels already consumed by target 422 * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target 423 * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target 424 * @param type the type of input which cause this scroll event 425 */ 426 public static void onNestedScroll(ViewParent parent, View target, int dxConsumed, 427 int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 428 if (parent instanceof NestedScrollingParent2) { 429 // First try the NestedScrollingParent2 API 430 ((NestedScrollingParent2) parent).onNestedScroll(target, dxConsumed, dyConsumed, 431 dxUnconsumed, dyUnconsumed, type); 432 } else if (type == ViewCompat.TYPE_TOUCH) { 433 // Else if the type is the default (touch), try the NestedScrollingParent API 434 IMPL.onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 435 } 436 } 437 438 /** 439 * React to a nested scroll in progress before the target view consumes a portion of the scroll. 440 * 441 * <p>When working with nested scrolling often the parent view may want an opportunity 442 * to consume the scroll before the nested scrolling child does. An example of this is a 443 * drawer that contains a scrollable list. The user will want to be able to scroll the list 444 * fully into view before the list itself begins scrolling.</p> 445 * 446 * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes 447 * {@link ViewCompat#dispatchNestedPreScroll(View, int, int, int[], int[])}. The implementation 448 * should report how any pixels of the scroll reported by dx, dy were consumed in the 449 * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy. 450 * This parameter will never be null. Initial values for consumed[0] and consumed[1] 451 * will always be 0.</p> 452 * 453 * @param target View that initiated the nested scroll 454 * @param dx Horizontal scroll distance in pixels 455 * @param dy Vertical scroll distance in pixels 456 * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent 457 * @param type the type of input which cause this scroll event 458 */ 459 public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, 460 int[] consumed, int type) { 461 if (parent instanceof NestedScrollingParent2) { 462 // First try the NestedScrollingParent2 API 463 ((NestedScrollingParent2) parent).onNestedPreScroll(target, dx, dy, consumed, type); 464 } else if (type == ViewCompat.TYPE_TOUCH) { 465 // Else if the type is the default (touch), try the NestedScrollingParent API 466 IMPL.onNestedPreScroll(parent, target, dx, dy, consumed); 467 } 468 } 469 470 /** 471 * Request a fling from a nested scroll. 472 * 473 * <p>This method signifies that a nested scrolling child has detected suitable conditions 474 * for a fling. Generally this means that a touch scroll has ended with a 475 * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds 476 * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity} 477 * along a scrollable axis.</p> 478 * 479 * <p>If a nested scrolling child view would normally fling but it is at the edge of 480 * its own content, it can use this method to delegate the fling to its nested scrolling 481 * parent instead. The parent may optionally consume the fling or observe a child fling.</p> 482 * 483 * @param target View that initiated the nested scroll 484 * @param velocityX Horizontal velocity in pixels per second 485 * @param velocityY Vertical velocity in pixels per second 486 * @param consumed true if the child consumed the fling, false otherwise 487 * @return true if this parent consumed or otherwise reacted to the fling 488 */ 489 public static boolean onNestedFling(ViewParent parent, View target, float velocityX, 490 float velocityY, boolean consumed) { 491 return IMPL.onNestedFling(parent, target, velocityX, velocityY, consumed); 492 } 493 494 /** 495 * React to a nested fling before the target view consumes it. 496 * 497 * <p>This method siginfies that a nested scrolling child has detected a fling with the given 498 * velocity along each axis. Generally this means that a touch scroll has ended with a 499 * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds 500 * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity} 501 * along a scrollable axis.</p> 502 * 503 * <p>If a nested scrolling parent is consuming motion as part of a 504 * {@link #onNestedPreScroll(ViewParent, View, int, int, int[]) pre-scroll}, it may be 505 * appropriate for it to also consume the pre-fling to complete that same motion. By returning 506 * <code>true</code> from this method, the parent indicates that the child should not 507 * fling its own internal content as well.</p> 508 * 509 * @param target View that initiated the nested scroll 510 * @param velocityX Horizontal velocity in pixels per second 511 * @param velocityY Vertical velocity in pixels per second 512 * @return true if this parent consumed the fling ahead of the target view 513 */ 514 public static boolean onNestedPreFling(ViewParent parent, View target, float velocityX, 515 float velocityY) { 516 return IMPL.onNestedPreFling(parent, target, velocityX, velocityY); 517 } 518 519 /** 520 * Notifies a view parent that the accessibility state of one of its 521 * descendants has changed and that the structure of the subtree is 522 * different. 523 * @param child The direct child whose subtree has changed. 524 * @param source The descendant view that changed. 525 * @param changeType A bit mask of the types of changes that occurred. One 526 * or more of: 527 * <ul> 528 * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION} 529 * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE} 530 * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT} 531 * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} 532 * </ul> 533 */ 534 public static void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child, 535 View source, int changeType) { 536 IMPL.notifySubtreeAccessibilityStateChanged(parent, child, source, changeType); 537 } 538} 539