NestedScrollingChildHelper.java revision 76daed103193a1756535d1f59b165e98e1d17445
1/* 2 * Copyright (C) 2015 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 17 18package android.support.v4.view; 19 20import static android.support.v4.view.ViewCompat.TYPE_NON_TOUCH; 21import static android.support.v4.view.ViewCompat.TYPE_TOUCH; 22 23import android.support.annotation.NonNull; 24import android.support.annotation.Nullable; 25import android.support.v4.view.ViewCompat.NestedScrollType; 26import android.support.v4.view.ViewCompat.ScrollAxis; 27import android.view.View; 28import android.view.ViewParent; 29 30/** 31 * Helper class for implementing nested scrolling child views compatible with Android platform 32 * versions earlier than Android 5.0 Lollipop (API 21). 33 * 34 * <p>{@link android.view.View View} subclasses should instantiate a final instance of this 35 * class as a field at construction. For each <code>View</code> method that has a matching 36 * method signature in this class, delegate the operation to the helper instance in an overridden 37 * method implementation. This implements the standard framework policy for nested scrolling.</p> 38 * 39 * <p>Views invoking nested scrolling functionality should always do so from the relevant 40 * {@link android.support.v4.view.ViewCompat}, {@link android.support.v4.view.ViewGroupCompat} or 41 * {@link android.support.v4.view.ViewParentCompat} compatibility 42 * shim static methods. This ensures interoperability with nested scrolling views on Android 43 * 5.0 Lollipop and newer.</p> 44 */ 45public class NestedScrollingChildHelper { 46 private ViewParent mNestedScrollingParentTouch; 47 private ViewParent mNestedScrollingParentNonTouch; 48 private final View mView; 49 private boolean mIsNestedScrollingEnabled; 50 private int[] mTempNestedScrollConsumed; 51 52 /** 53 * Construct a new helper for a given view. 54 */ 55 public NestedScrollingChildHelper(@NonNull View view) { 56 mView = view; 57 } 58 59 /** 60 * Enable nested scrolling. 61 * 62 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 63 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 64 * signature to implement the standard policy.</p> 65 * 66 * @param enabled true to enable nested scrolling dispatch from this view, false otherwise 67 */ 68 public void setNestedScrollingEnabled(boolean enabled) { 69 if (mIsNestedScrollingEnabled) { 70 ViewCompat.stopNestedScroll(mView); 71 } 72 mIsNestedScrollingEnabled = enabled; 73 } 74 75 /** 76 * Check if nested scrolling is enabled for this view. 77 * 78 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 79 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 80 * signature to implement the standard policy.</p> 81 * 82 * @return true if nested scrolling is enabled for this view 83 */ 84 public boolean isNestedScrollingEnabled() { 85 return mIsNestedScrollingEnabled; 86 } 87 88 /** 89 * Check if this view has a nested scrolling parent view currently receiving events for 90 * a nested scroll in progress with the type of touch. 91 * 92 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 93 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 94 * signature to implement the standard policy.</p> 95 * 96 * @return true if this view has a nested scrolling parent, false otherwise 97 */ 98 public boolean hasNestedScrollingParent() { 99 return hasNestedScrollingParent(TYPE_TOUCH); 100 } 101 102 /** 103 * Check if this view has a nested scrolling parent view currently receiving events for 104 * a nested scroll in progress with the given type. 105 * 106 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 107 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 108 * signature to implement the standard policy.</p> 109 * 110 * @return true if this view has a nested scrolling parent, false otherwise 111 */ 112 public boolean hasNestedScrollingParent(@NestedScrollType int type) { 113 return getNestedScrollingParentForType(type) != null; 114 } 115 116 /** 117 * Start a new nested scroll for this view. 118 * 119 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 120 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 121 * signature to implement the standard policy.</p> 122 * 123 * @param axes Supported nested scroll axes. 124 * See {@link android.support.v4.view.NestedScrollingChild#startNestedScroll(int)}. 125 * @return true if a cooperating parent view was found and nested scrolling started successfully 126 */ 127 public boolean startNestedScroll(@ScrollAxis int axes) { 128 return startNestedScroll(axes, TYPE_TOUCH); 129 } 130 131 /** 132 * Start a new nested scroll for this view. 133 * 134 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 135 * method/{@link android.support.v4.view.NestedScrollingChild2} interface method with the same 136 * signature to implement the standard policy.</p> 137 * 138 * @param axes Supported nested scroll axes. 139 * See {@link android.support.v4.view.NestedScrollingChild2#startNestedScroll(int, 140 * int)}. 141 * @return true if a cooperating parent view was found and nested scrolling started successfully 142 */ 143 public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) { 144 if (hasNestedScrollingParent(type)) { 145 // Already in progress 146 return true; 147 } 148 if (isNestedScrollingEnabled()) { 149 ViewParent p = mView.getParent(); 150 View child = mView; 151 while (p != null) { 152 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) { 153 setNestedScrollingParentForType(type, p); 154 ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type); 155 return true; 156 } 157 if (p instanceof View) { 158 child = (View) p; 159 } 160 p = p.getParent(); 161 } 162 } 163 return false; 164 } 165 166 /** 167 * Stop a nested scroll in progress. 168 * 169 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 170 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 171 * signature to implement the standard policy.</p> 172 */ 173 public void stopNestedScroll() { 174 stopNestedScroll(TYPE_TOUCH); 175 } 176 177 /** 178 * Stop a nested scroll in progress. 179 * 180 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 181 * method/{@link android.support.v4.view.NestedScrollingChild2} interface method with the same 182 * signature to implement the standard policy.</p> 183 */ 184 public void stopNestedScroll(@NestedScrollType int type) { 185 ViewParent parent = getNestedScrollingParentForType(type); 186 if (parent != null) { 187 ViewParentCompat.onStopNestedScroll(parent, mView, type); 188 setNestedScrollingParentForType(type, null); 189 } 190 } 191 192 /** 193 * Dispatch one step of a nested scrolling operation to the current nested scrolling parent. 194 * 195 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 196 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 197 * signature to implement the standard policy.</p> 198 * 199 * @return true if the parent consumed any of the nested scroll 200 */ 201 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, 202 int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) { 203 return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 204 offsetInWindow, TYPE_TOUCH); 205 } 206 207 /** 208 * Dispatch one step of a nested scrolling operation to the current nested scrolling parent. 209 * 210 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 211 * method/{@link android.support.v4.view.NestedScrollingChild2} interface method with the same 212 * signature to implement the standard policy.</p> 213 * 214 * @return true if the parent consumed any of the nested scroll 215 */ 216 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, 217 int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, 218 @NestedScrollType int type) { 219 if (isNestedScrollingEnabled()) { 220 final ViewParent parent = getNestedScrollingParentForType(type); 221 if (parent == null) { 222 return false; 223 } 224 225 if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) { 226 int startX = 0; 227 int startY = 0; 228 if (offsetInWindow != null) { 229 mView.getLocationInWindow(offsetInWindow); 230 startX = offsetInWindow[0]; 231 startY = offsetInWindow[1]; 232 } 233 234 ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, 235 dyConsumed, dxUnconsumed, dyUnconsumed, type); 236 237 if (offsetInWindow != null) { 238 mView.getLocationInWindow(offsetInWindow); 239 offsetInWindow[0] -= startX; 240 offsetInWindow[1] -= startY; 241 } 242 return true; 243 } else if (offsetInWindow != null) { 244 // No motion, no dispatch. Keep offsetInWindow up to date. 245 offsetInWindow[0] = 0; 246 offsetInWindow[1] = 0; 247 } 248 } 249 return false; 250 } 251 252 /** 253 * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent. 254 * 255 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 256 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 257 * signature to implement the standard policy.</p> 258 * 259 * @return true if the parent consumed any of the nested scroll 260 */ 261 public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, 262 @Nullable int[] offsetInWindow) { 263 return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH); 264 } 265 266 /** 267 * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent. 268 * 269 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 270 * method/{@link android.support.v4.view.NestedScrollingChild2} interface method with the same 271 * signature to implement the standard policy.</p> 272 * 273 * @return true if the parent consumed any of the nested scroll 274 */ 275 public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, 276 @Nullable int[] offsetInWindow, @NestedScrollType int type) { 277 if (isNestedScrollingEnabled()) { 278 final ViewParent parent = getNestedScrollingParentForType(type); 279 if (parent == null) { 280 return false; 281 } 282 283 if (dx != 0 || dy != 0) { 284 int startX = 0; 285 int startY = 0; 286 if (offsetInWindow != null) { 287 mView.getLocationInWindow(offsetInWindow); 288 startX = offsetInWindow[0]; 289 startY = offsetInWindow[1]; 290 } 291 292 if (consumed == null) { 293 if (mTempNestedScrollConsumed == null) { 294 mTempNestedScrollConsumed = new int[2]; 295 } 296 consumed = mTempNestedScrollConsumed; 297 } 298 consumed[0] = 0; 299 consumed[1] = 0; 300 ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type); 301 302 if (offsetInWindow != null) { 303 mView.getLocationInWindow(offsetInWindow); 304 offsetInWindow[0] -= startX; 305 offsetInWindow[1] -= startY; 306 } 307 return consumed[0] != 0 || consumed[1] != 0; 308 } else if (offsetInWindow != null) { 309 offsetInWindow[0] = 0; 310 offsetInWindow[1] = 0; 311 } 312 } 313 return false; 314 } 315 316 /** 317 * Dispatch a nested fling operation to the current nested scrolling parent. 318 * 319 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 320 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 321 * signature to implement the standard policy.</p> 322 * 323 * @return true if the parent consumed the nested fling 324 */ 325 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 326 if (isNestedScrollingEnabled()) { 327 ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH); 328 if (parent != null) { 329 return ViewParentCompat.onNestedFling(parent, mView, velocityX, 330 velocityY, consumed); 331 } 332 } 333 return false; 334 } 335 336 /** 337 * Dispatch a nested pre-fling operation to the current nested scrolling parent. 338 * 339 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 340 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 341 * signature to implement the standard policy.</p> 342 * 343 * @return true if the parent consumed the nested fling 344 */ 345 public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 346 if (isNestedScrollingEnabled()) { 347 ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH); 348 if (parent != null) { 349 return ViewParentCompat.onNestedPreFling(parent, mView, velocityX, 350 velocityY); 351 } 352 } 353 return false; 354 } 355 356 /** 357 * View subclasses should always call this method on their 358 * <code>NestedScrollingChildHelper</code> when detached from a window. 359 * 360 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 361 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 362 * signature to implement the standard policy.</p> 363 */ 364 public void onDetachedFromWindow() { 365 ViewCompat.stopNestedScroll(mView); 366 } 367 368 /** 369 * Called when a nested scrolling child stops its current nested scroll operation. 370 * 371 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 372 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 373 * signature to implement the standard policy.</p> 374 * 375 * @param child Child view stopping its nested scroll. This may not be a direct child view. 376 */ 377 public void onStopNestedScroll(@NonNull View child) { 378 ViewCompat.stopNestedScroll(mView); 379 } 380 381 private ViewParent getNestedScrollingParentForType(@NestedScrollType int type) { 382 switch (type) { 383 case TYPE_TOUCH: 384 return mNestedScrollingParentTouch; 385 case TYPE_NON_TOUCH: 386 return mNestedScrollingParentNonTouch; 387 } 388 return null; 389 } 390 391 private void setNestedScrollingParentForType(@NestedScrollType int type, ViewParent p) { 392 switch (type) { 393 case TYPE_TOUCH: 394 mNestedScrollingParentTouch = p; 395 break; 396 case TYPE_NON_TOUCH: 397 mNestedScrollingParentNonTouch = p; 398 break; 399 } 400 } 401} 402