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