NestedScrollingChildHelper.java revision ff22d81f6561f6cdd2a91eb63238c41079927a22
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 android.view.View; 21import android.view.ViewParent; 22 23/** 24 * Helper class for implementing nested scrolling child views compatible with Android platform 25 * versions earlier than Android 5.0 Lollipop (API 21). 26 * 27 * <p>{@link android.view.View View} subclasses should instantiate a final instance of this 28 * class as a field at construction. For each <code>View</code> method that has a matching 29 * method signature in this class, delegate the operation to the helper instance in an overriden 30 * method implementation. This implements the standard framework policy for nested scrolling.</p> 31 * 32 * <p>Views invoking nested scrolling functionality should always do so from the relevant 33 * {@link android.support.v4.view.ViewCompat}, {@link android.support.v4.view.ViewGroupCompat} or 34 * {@link android.support.v4.view.ViewParentCompat} compatibility 35 * shim static methods. This ensures interoperability with nested scrolling views on Android 36 * 5.0 Lollipop and newer.</p> 37 */ 38public class NestedScrollingChildHelper { 39 private final View mView; 40 private ViewParent mNestedScrollingParent; 41 private boolean mIsNestedScrollingEnabled; 42 private int[] mTempNestedScrollConsumed; 43 44 /** 45 * Construct a new helper for a given view. 46 */ 47 public NestedScrollingChildHelper(View view) { 48 mView = view; 49 } 50 51 /** 52 * Enable nested scrolling. 53 * 54 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 55 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 56 * signature to implement the standard policy.</p> 57 * 58 * @param enabled true to enable nested scrolling dispatch from this view, false otherwise 59 */ 60 public void setNestedScrollingEnabled(boolean enabled) { 61 if (mIsNestedScrollingEnabled) { 62 ViewCompat.stopNestedScroll(mView); 63 } 64 mIsNestedScrollingEnabled = enabled; 65 } 66 67 /** 68 * Check if nested scrolling is enabled for this view. 69 * 70 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 71 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 72 * signature to implement the standard policy.</p> 73 * 74 * @return true if nested scrolling is enabled for this view 75 */ 76 public boolean isNestedScrollingEnabled() { 77 return mIsNestedScrollingEnabled; 78 } 79 80 /** 81 * Check if this view has a nested scrolling parent view currently receiving events for 82 * a nested scroll in progress. 83 * 84 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 85 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 86 * signature to implement the standard policy.</p> 87 * 88 * @return true if this view has a nested scrolling parent, false otherwise 89 */ 90 public boolean hasNestedScrollingParent() { 91 return mNestedScrollingParent != null; 92 } 93 94 /** 95 * Start a new nested scroll for this view. 96 * 97 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 98 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 99 * signature to implement the standard policy.</p> 100 * 101 * @param axes Supported nested scroll axes. 102 * See {@link android.support.v4.view.NestedScrollingChild#startNestedScroll(int)}. 103 * @return true if a cooperating parent view was found and nested scrolling started successfully 104 */ 105 public boolean startNestedScroll(int axes) { 106 if (hasNestedScrollingParent()) { 107 // Already in progress 108 return true; 109 } 110 if (isNestedScrollingEnabled()) { 111 ViewParent p = mView.getParent(); 112 View child = mView; 113 while (p != null) { 114 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { 115 mNestedScrollingParent = p; 116 ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); 117 return true; 118 } 119 if (p instanceof View) { 120 child = (View) p; 121 } 122 p = p.getParent(); 123 } 124 } 125 return false; 126 } 127 128 /** 129 * Stop a nested scroll in progress. 130 * 131 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 132 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 133 * signature to implement the standard policy.</p> 134 */ 135 public void stopNestedScroll() { 136 if (mNestedScrollingParent != null) { 137 ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView); 138 mNestedScrollingParent = null; 139 } 140 } 141 142 /** 143 * Dispatch one step of a nested scrolling operation to the current nested scrolling parent. 144 * 145 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 146 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 147 * signature to implement the standard policy.</p> 148 * 149 * @return true if the parent consumed any of the nested scroll 150 */ 151 public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, 152 int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { 153 if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { 154 if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) { 155 int startX = 0; 156 int startY = 0; 157 if (offsetInWindow != null) { 158 mView.getLocationInWindow(offsetInWindow); 159 startX = offsetInWindow[0]; 160 startY = offsetInWindow[1]; 161 } 162 163 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed, 164 dyConsumed, dxUnconsumed, dyUnconsumed); 165 166 if (offsetInWindow != null) { 167 mView.getLocationInWindow(offsetInWindow); 168 offsetInWindow[0] -= startX; 169 offsetInWindow[1] -= startY; 170 } 171 return true; 172 } else if (offsetInWindow != null) { 173 // No motion, no dispatch. Keep offsetInWindow up to date. 174 offsetInWindow[0] = 0; 175 offsetInWindow[1] = 0; 176 } 177 } 178 return false; 179 } 180 181 /** 182 * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent. 183 * 184 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 185 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 186 * signature to implement the standard policy.</p> 187 * 188 * @return true if the parent consumed any of the nested scroll 189 */ 190 public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 191 if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { 192 if (dx != 0 || dy != 0) { 193 int startX = 0; 194 int startY = 0; 195 if (offsetInWindow != null) { 196 mView.getLocationInWindow(offsetInWindow); 197 startX = offsetInWindow[0]; 198 startY = offsetInWindow[1]; 199 } 200 201 if (consumed == null) { 202 if (mTempNestedScrollConsumed == null) { 203 mTempNestedScrollConsumed = new int[2]; 204 } 205 consumed = mTempNestedScrollConsumed; 206 } 207 consumed[0] = 0; 208 consumed[1] = 0; 209 ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed); 210 211 if (offsetInWindow != null) { 212 mView.getLocationInWindow(offsetInWindow); 213 offsetInWindow[0] -= startX; 214 offsetInWindow[1] -= startY; 215 } 216 return consumed[0] != 0 || consumed[1] != 0; 217 } else if (offsetInWindow != null) { 218 offsetInWindow[0] = 0; 219 offsetInWindow[1] = 0; 220 } 221 } 222 return false; 223 } 224 225 /** 226 * Dispatch a nested fling operation to the current nested scrolling parent. 227 * 228 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 229 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 230 * signature to implement the standard policy.</p> 231 * 232 * @return true if the parent consumed the nested fling 233 */ 234 public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 235 if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { 236 return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX, 237 velocityY, consumed); 238 } 239 return false; 240 } 241 242 /** 243 * Dispatch a nested pre-fling operation to the current nested scrolling parent. 244 * 245 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 246 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 247 * signature to implement the standard policy.</p> 248 * 249 * @return true if the parent consumed the nested fling 250 */ 251 public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 252 if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { 253 return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX, 254 velocityY); 255 } 256 return false; 257 } 258 259 /** 260 * View subclasses should always call this method on their 261 * <code>NestedScrollingChildHelper</code> when detached from a window. 262 * 263 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 264 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 265 * signature to implement the standard policy.</p> 266 */ 267 public void onDetachedFromWindow() { 268 ViewCompat.stopNestedScroll(mView); 269 } 270 271 /** 272 * Called when a nested scrolling child stops its current nested scroll operation. 273 * 274 * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 275 * method/{@link android.support.v4.view.NestedScrollingChild} interface method with the same 276 * signature to implement the standard policy.</p> 277 * 278 * @param child Child view stopping its nested scroll. This may not be a direct child view. 279 */ 280 public void onStopNestedScroll(View child) { 281 ViewCompat.stopNestedScroll(mView); 282 } 283} 284