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