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