1 /*
2  * Copyright 2018 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 package androidx.recyclerview.widget;
18 
19 import static org.hamcrest.CoreMatchers.is;
20 import static org.hamcrest.MatcherAssert.assertThat;
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.anyFloat;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.doAnswer;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.when;
30 
31 import android.content.Context;
32 import android.os.SystemClock;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewConfiguration;
36 import android.view.ViewGroup;
37 import android.widget.FrameLayout;
38 
39 import androidx.core.view.NestedScrollingChild3;
40 import androidx.core.view.NestedScrollingParent3;
41 import androidx.core.view.ViewCompat;
42 import androidx.test.core.app.ApplicationProvider;
43 import androidx.test.ext.junit.runners.AndroidJUnit4;
44 import androidx.test.filters.LargeTest;
45 import androidx.testutils.Direction;
46 import androidx.testutils.FlingData;
47 import androidx.testutils.MotionEventData;
48 import androidx.testutils.SimpleGestureGeneratorKt;
49 
50 import org.jspecify.annotations.NonNull;
51 import org.jspecify.annotations.Nullable;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.InOrder;
55 import org.mockito.Mockito;
56 import org.mockito.invocation.InvocationOnMock;
57 import org.mockito.stubbing.Answer;
58 
59 import java.util.List;
60 
61 /**
62  * Small integration tests that verifies that {@link RecyclerView} interacts with the latest
63  * version of the nested scroll parents correctly.
64  */
65 @RunWith(AndroidJUnit4.class)
66 @LargeTest
67 public class RecyclerViewNestedScrollingChildTest {
68 
69     private NestedScrollingSpyView mParentSpy;
70     private RecyclerView mRecyclerView;
71     private int mTouchSlop;
72 
setup(boolean vertical, int scrollDistance, boolean parentAccepts)73     private void setup(boolean vertical, int scrollDistance, boolean parentAccepts) {
74 
75         Context context = ApplicationProvider.getApplicationContext();
76 
77         // Create views
78 
79         mTouchSlop = ViewConfiguration.get(
80                 ApplicationProvider.getApplicationContext()).getScaledTouchSlop();
81 
82         mRecyclerView = new RecyclerView(context);
83         mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
84         mRecyclerView.setMinimumWidth(1000);
85         mRecyclerView.setMinimumHeight(1000);
86 
87         mParentSpy = Mockito.spy(new NestedScrollingSpyView(context));
88         mParentSpy.setMinimumWidth(1000);
89         mParentSpy.setMinimumHeight(1000);
90 
91         // Setup RecyclerView
92         int orientation = vertical ? RecyclerView.VERTICAL : RecyclerView.HORIZONTAL;
93         mRecyclerView.setLayoutManager(new LinearLayoutManager(context, orientation, false));
94         mRecyclerView.setAdapter(new TestAdapter(context, 1000 + scrollDistance, 1, vertical));
95 
96         // Create view hierarchy
97         mParentSpy.addView(mRecyclerView);
98 
99         //  Measure and layout
100         int measureSpecWidth = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY);
101         int measureSpecHeight = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY);
102         mParentSpy.measure(measureSpecWidth, measureSpecHeight);
103         mParentSpy.layout(0, 0, 1000, 1000);
104 
105         when(mParentSpy.onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt()))
106                 .thenReturn(parentAccepts);
107     }
108 
109     @Test
uiFingerDown_vertical_parentHasNestedScrollingChildWithTypeTouch()110     public void uiFingerDown_vertical_parentHasNestedScrollingChildWithTypeTouch() {
111         setup(true, 100, true);
112         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
113 
114         mRecyclerView.dispatchTouchEvent(down);
115 
116         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_VERTICAL));
117         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_NONE));
118     }
119 
120     @Test
uiFingerDown_horizontal_parentHasNestedScrollingChildWithTypeTouch()121     public void uiFingerDown_horizontal_parentHasNestedScrollingChildWithTypeTouch() {
122         setup(false, 100, true);
123         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
124 
125         mRecyclerView.dispatchTouchEvent(down);
126 
127         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_HORIZONTAL));
128         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_NONE));
129     }
130 
131     @Test
uiFingerDown_parentRejects_parentDoesNotHaveNestedScrollingChild()132     public void uiFingerDown_parentRejects_parentDoesNotHaveNestedScrollingChild() {
133         setup(true, 100, false);
134         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
135 
136         mRecyclerView.dispatchTouchEvent(down);
137 
138         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_NONE));
139         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_NONE));
140     }
141 
142     @Test
uiFingerUp_afterFingerDown_parentDoesNotHaveNestedScrollingChild()143     public void uiFingerUp_afterFingerDown_parentDoesNotHaveNestedScrollingChild() {
144         setup(true, 100, true);
145         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
146         MotionEvent up = MotionEvent.obtain(0, 100, MotionEvent.ACTION_UP, 500, 500, 0);
147         mRecyclerView.dispatchTouchEvent(down);
148 
149         mRecyclerView.dispatchTouchEvent(up);
150 
151         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_NONE));
152         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_NONE));
153     }
154 
155     @Test
uiFingerScroll_vertical_parentOnNestedPreScrollCalledCorrectly()156     public void uiFingerScroll_vertical_parentOnNestedPreScrollCalledCorrectly() {
157         setup(true, 100, true);
158         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
159         MotionEvent move = MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 500, 300, 0);
160 
161         mRecyclerView.dispatchTouchEvent(down);
162         mRecyclerView.dispatchTouchEvent(move);
163 
164         // Can't verify 'consumed' parameter values due to mutation, so instead capturing actual
165         // values manually in the the NestedScrollingSpyView object.
166         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(0), eq(200 - mTouchSlop),
167                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
168         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
169         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
170 
171         move = MotionEvent.obtain(0, 200, MotionEvent.ACTION_MOVE, 500, 300 - mTouchSlop / 2, 0);
172         mRecyclerView.dispatchTouchEvent(move);
173 
174         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(0), eq(mTouchSlop / 2),
175                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
176         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
177         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
178     }
179 
180     @Test
uiFingerScroll_horizontal_parentOnNestedPreScrollCalledCorrectly()181     public void uiFingerScroll_horizontal_parentOnNestedPreScrollCalledCorrectly() {
182         setup(false, 100, true);
183         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
184         MotionEvent move = MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 300, 500, 0);
185 
186         mRecyclerView.dispatchTouchEvent(down);
187         mRecyclerView.dispatchTouchEvent(move);
188 
189         // Can't verify 'consumed' parameter values due to mutation, so instead capturing actual
190         // values manually in the the NestedScrollingSpyView object.
191         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(200 - mTouchSlop), eq(0),
192                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
193         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
194         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
195 
196         move = MotionEvent.obtain(0, 200, MotionEvent.ACTION_MOVE, 300 - mTouchSlop / 2, 500, 0);
197         mRecyclerView.dispatchTouchEvent(move);
198 
199         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(mTouchSlop / 2), eq(0),
200                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
201         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
202         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
203     }
204 
205     @Test
uiFingerScroll_scrollsBeyondLimitVertical_parentOnNestedScrollCalledCorrectly()206     public void uiFingerScroll_scrollsBeyondLimitVertical_parentOnNestedScrollCalledCorrectly() {
207         setup(true, 100, true);
208         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
209         MotionEvent move =
210                 MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 500, 300 - mTouchSlop, 0);
211 
212         mParentSpy.dispatchTouchEvent(down);
213         mParentSpy.dispatchTouchEvent(move);
214 
215         verify(mParentSpy).onNestedScroll(mRecyclerView, 0, 100, 0, 100, ViewCompat.TYPE_TOUCH,
216                 new int[]{0, 0});
217     }
218 
219     @Test
uiFingerScroll_scrollsBeyondLimitHorizontal_parentOnNestedScrollCalledCorrectly()220     public void uiFingerScroll_scrollsBeyondLimitHorizontal_parentOnNestedScrollCalledCorrectly() {
221         setup(false, 100, true);
222         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
223         MotionEvent move =
224                 MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 300 - mTouchSlop, 500, 0);
225 
226         mParentSpy.dispatchTouchEvent(down);
227         mParentSpy.dispatchTouchEvent(move);
228 
229         verify(mParentSpy).onNestedScroll(mRecyclerView, 100, 0, 100, 0, ViewCompat.TYPE_TOUCH,
230                 new int[]{0, 0});
231     }
232 
233     @Test
uiFingerScroll_scrollsWithinLimitVertical_parentOnNestedScrollCalledCorrectly()234     public void uiFingerScroll_scrollsWithinLimitVertical_parentOnNestedScrollCalledCorrectly() {
235         setup(true, 100, true);
236         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
237         MotionEvent move =
238                 MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 500, 450 - mTouchSlop, 0);
239 
240         mParentSpy.dispatchTouchEvent(down);
241         mParentSpy.dispatchTouchEvent(move);
242 
243         verify(mParentSpy).onNestedScroll(mRecyclerView, 0, 50, 0, 0, ViewCompat.TYPE_TOUCH,
244                 new int[]{0, 0});
245     }
246 
247     @Test
uiFingerScroll_scrollsWithinLimitHorizontal_parentOnNestedScrollCalledCorrectly()248     public void uiFingerScroll_scrollsWithinLimitHorizontal_parentOnNestedScrollCalledCorrectly() {
249         setup(false, 100, true);
250         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
251         MotionEvent move =
252                 MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 450 - mTouchSlop, 500, 0);
253 
254         mParentSpy.dispatchTouchEvent(down);
255         mParentSpy.dispatchTouchEvent(move);
256 
257         verify(mParentSpy).onNestedScroll(mRecyclerView, 50, 0, 0, 0, ViewCompat.TYPE_TOUCH,
258                 new int[]{0, 0});
259     }
260 
261     @Test
uiFingerScroll_vertical_preSelfPostChainWorks()262     public void uiFingerScroll_vertical_preSelfPostChainWorks() {
263         setup(true, 100, true);
264         doAnswer(new Answer() {
265             public Object answer(InvocationOnMock invocation) {
266                 Object[] args = invocation.getArguments();
267                 ((int[]) args[3])[1] = 50;
268                 return null;
269             }
270         }).when(mParentSpy)
271                 .onNestedPreScroll(any(View.class), anyInt(), anyInt(), any(int[].class), anyInt());
272         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
273         MotionEvent move =
274                 MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 500, 300, 0);
275 
276         mRecyclerView.dispatchTouchEvent(down);
277         mRecyclerView.dispatchTouchEvent(move);
278 
279         // Can't verify 'consumed' parameter values due to mutation, so instead capturing actual
280         // values manually in the the NestedScrollingSpyView object.
281         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(0), eq(200 - mTouchSlop),
282                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
283         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
284         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
285 
286         verify(mParentSpy).onNestedScroll(mRecyclerView, 0, 100, 0, 50 - mTouchSlop,
287                 ViewCompat.TYPE_TOUCH, new int[]{0, 0});
288     }
289 
290     @Test
uiFingerScroll_horizontal_preSelfPostChainWorks()291     public void uiFingerScroll_horizontal_preSelfPostChainWorks() {
292         setup(false, 100, true);
293         doAnswer(new Answer() {
294             public Object answer(InvocationOnMock invocation) {
295                 Object[] args = invocation.getArguments();
296                 ((int[]) args[3])[0] = 50;
297                 return null;
298             }
299         }).when(mParentSpy)
300                 .onNestedPreScroll(any(View.class), anyInt(), anyInt(), any(int[].class), anyInt());
301         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 500, 500, 0);
302         MotionEvent move = MotionEvent.obtain(0, 100, MotionEvent.ACTION_MOVE, 300, 500, 0);
303 
304         mRecyclerView.dispatchTouchEvent(down);
305         mRecyclerView.dispatchTouchEvent(move);
306 
307         // Can't verify 'consumed' parameter values due to mutation, so instead capturing actual
308         // values manually in the the NestedScrollingSpyView object.
309         verify(mParentSpy).onNestedPreScroll(eq(mRecyclerView), eq(200 - mTouchSlop), eq(0),
310                 any(int[].class), eq(ViewCompat.TYPE_TOUCH));
311         assertThat(mParentSpy.onNestedPreScrollConsumedX, is(0));
312         assertThat(mParentSpy.onNestedPreScrollConsumedY, is(0));
313 
314         verify(mParentSpy).onNestedScroll(mRecyclerView, 100, 0, 50 - mTouchSlop, 0,
315                 ViewCompat.TYPE_TOUCH, new int[]{0, 0});
316     }
317 
318     @Test
uiFling_vertical_parentHasNestedScrollingChildWithTypeFling()319     public void uiFling_vertical_parentHasNestedScrollingChildWithTypeFling() {
320         setup(true, 100, true);
321         long startTime = SystemClock.uptimeMillis();
322 
323         SimpleGestureGeneratorKt
324                 .simulateFling(mRecyclerView, startTime, 500, 500, Direction.UP);
325 
326         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_NONE));
327         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_VERTICAL));
328     }
329 
330     @Test
uiFling_horizontal_parentHasNestedScrollingChildWithTypeFling()331     public void uiFling_horizontal_parentHasNestedScrollingChildWithTypeFling() {
332         setup(false, 100, true);
333         long startTime = SystemClock.uptimeMillis();
334 
335         SimpleGestureGeneratorKt
336                 .simulateFling(mRecyclerView, startTime, 500, 500, Direction.LEFT);
337 
338         assertThat(mParentSpy.axesForTypeTouch, is(ViewCompat.SCROLL_AXIS_NONE));
339         assertThat(mParentSpy.axesForTypeNonTouch, is(ViewCompat.SCROLL_AXIS_HORIZONTAL));
340     }
341 
342     @Test
uiFling_callsNestedFlingsCorrectly()343     public void uiFling_callsNestedFlingsCorrectly() {
344         setup(true, 100, true);
345         long startTime = SystemClock.uptimeMillis();
346 
347         SimpleGestureGeneratorKt
348                 .simulateFling(mRecyclerView, startTime, 500, 500, Direction.UP);
349 
350         InOrder inOrder = Mockito.inOrder(mParentSpy);
351         inOrder.verify(mParentSpy).onNestedPreFling(
352                 eq(mRecyclerView),
353                 eq(0f),
354                 anyFloat());
355         inOrder.verify(mParentSpy).onNestedFling(
356                 eq(mRecyclerView),
357                 eq(0f),
358                 anyFloat(),
359                 eq(true));
360     }
361 
362     @Test
uiDown_duringFling_stopsNestedScrolling()363     public void uiDown_duringFling_stopsNestedScrolling() {
364 
365         // Arrange
366 
367         setup(true, 1000, true);
368 
369         final Context context = ApplicationProvider.getApplicationContext();
370         FlingData flingData = SimpleGestureGeneratorKt.generateFlingData(context);
371 
372         final long firstDownTime = SystemClock.uptimeMillis();
373         // Should be after fling events occurred.
374         final long secondDownTime = firstDownTime + flingData.getTime() + 100;
375 
376         List<MotionEventData> motionEventData = SimpleGestureGeneratorKt
377                 .generateFlingMotionEventData(flingData, 500, 500, Direction.UP);
378         SimpleGestureGeneratorKt
379                 .dispatchTouchEvents(mRecyclerView, firstDownTime, motionEventData);
380 
381         // Assumption check that onStopNestedScroll has not yet been called of type TYPE_NON_TOUCH.
382         verify(mParentSpy, never())
383                 .onStopNestedScroll(mRecyclerView, ViewCompat.TYPE_NON_TOUCH);
384 
385         // Act
386 
387         MotionEvent down = MotionEvent.obtain(
388                 secondDownTime,
389                 secondDownTime,
390                 MotionEvent.ACTION_DOWN,
391                 500,
392                 500,
393                 0);
394         mRecyclerView.dispatchTouchEvent(down);
395 
396         // Assert
397 
398         verify(mParentSpy).onStopNestedScroll(mRecyclerView, ViewCompat.TYPE_NON_TOUCH);
399     }
400 
401     @Test
uiFlings_parentReturnsFalseForOnNestedPreFling_onNestedFlingCalled()402     public void uiFlings_parentReturnsFalseForOnNestedPreFling_onNestedFlingCalled() {
403         uiFlings_returnValueOfOnNestedPreFlingDeterminesCallToOnNestedFling(false, true);
404     }
405 
406     @Test
uiFlings_parentReturnsTrueForOnNestedPreFling_onNestedFlingNotCalled()407     public void uiFlings_parentReturnsTrueForOnNestedPreFling_onNestedFlingNotCalled() {
408         uiFlings_returnValueOfOnNestedPreFlingDeterminesCallToOnNestedFling(true, false);
409     }
410 
uiFlings_returnValueOfOnNestedPreFlingDeterminesCallToOnNestedFling( boolean returnValue, boolean onNestedFlingCalled)411     private void uiFlings_returnValueOfOnNestedPreFlingDeterminesCallToOnNestedFling(
412             boolean returnValue, boolean onNestedFlingCalled) {
413         setup(true, 100, true);
414         when(mParentSpy.onNestedPreFling(eq(mRecyclerView), anyFloat(), anyFloat()))
415                 .thenReturn(returnValue);
416 
417         long startTime = SystemClock.uptimeMillis();
418         SimpleGestureGeneratorKt
419                 .simulateFling(mRecyclerView, startTime, 500, 500, Direction.UP);
420 
421         if (onNestedFlingCalled) {
422             verify(mParentSpy).onNestedFling(eq(mRecyclerView), anyFloat(), anyFloat(), eq(true));
423         } else {
424             verify(mParentSpy, never()).onNestedFling(any(View.class), anyFloat(), anyFloat(),
425                     anyBoolean());
426         }
427     }
428 
429     @Test
smoothScrollBy_doesNotStartNestedScrolling()430     public void smoothScrollBy_doesNotStartNestedScrolling() {
431         setup(true, 100, true);
432         mRecyclerView.smoothScrollBy(0, 100);
433         verify(mParentSpy, never()).onStartNestedScroll(
434                 any(View.class), any(View.class), anyInt(), anyInt());
435     }
436 
437     public class NestedScrollingSpyView extends FrameLayout implements NestedScrollingChild3,
438             NestedScrollingParent3 {
439 
440         public int axesForTypeTouch;
441         public int axesForTypeNonTouch;
442         public int onNestedPreScrollConsumedX;
443         public int onNestedPreScrollConsumedY;
444 
NestedScrollingSpyView(Context context)445         public NestedScrollingSpyView(Context context) {
446             super(context);
447         }
448 
449         @Override
onStartNestedScroll(@onNull View child, @NonNull View target, int axes, int type)450         public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
451                 int type) {
452             return false;
453         }
454 
455         @Override
onNestedScrollAccepted(@onNull View child, @NonNull View target, int axes, int type)456         public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
457                 int type) {
458             if (type == ViewCompat.TYPE_NON_TOUCH) {
459                 axesForTypeNonTouch = axes;
460             } else {
461                 axesForTypeTouch = axes;
462             }
463         }
464 
465         @Override
onStopNestedScroll(@onNull View target, int type)466         public void onStopNestedScroll(@NonNull View target, int type) {
467             if (type == ViewCompat.TYPE_NON_TOUCH) {
468                 axesForTypeNonTouch = ViewCompat.SCROLL_AXIS_NONE;
469             } else {
470                 axesForTypeTouch = ViewCompat.SCROLL_AXIS_NONE;
471             }
472         }
473 
474         @Override
onNestedScroll(@onNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type)475         public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
476                 int dxUnconsumed, int dyUnconsumed, int type) {
477 
478         }
479 
480         @Override
onNestedPreScroll(@onNull View target, int dx, int dy, int @NonNull [] consumed, int type)481         public void onNestedPreScroll(@NonNull View target, int dx, int dy,
482                 int @NonNull [] consumed, int type) {
483             onNestedPreScrollConsumedX = consumed[0];
484             onNestedPreScrollConsumedY = consumed[1];
485         }
486 
487         @Override
startNestedScroll(int axes, int type)488         public boolean startNestedScroll(int axes, int type) {
489             return false;
490         }
491 
492         @Override
stopNestedScroll(int type)493         public void stopNestedScroll(int type) {
494 
495         }
496 
497         @Override
hasNestedScrollingParent(int type)498         public boolean hasNestedScrollingParent(int type) {
499             return false;
500         }
501 
502         @Override
dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int @Nullable [] offsetInWindow, int type)503         public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
504                 int dyUnconsumed, int @Nullable [] offsetInWindow, int type) {
505             return false;
506         }
507 
508         @Override
dispatchNestedPreScroll(int dx, int dy, int @Nullable [] consumed, int @Nullable [] offsetInWindow, int type)509         public boolean dispatchNestedPreScroll(int dx, int dy, int @Nullable [] consumed,
510                 int @Nullable [] offsetInWindow, int type) {
511             return false;
512         }
513 
514         @Override
onNestedScroll(@onNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, int @NonNull [] consumed)515         public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
516                 int dxUnconsumed, int dyUnconsumed, int type, int @NonNull [] consumed) {
517         }
518 
519         @Override
dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int @Nullable [] offsetInWindow, int type, int @NonNull [] consumed)520         public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
521                 int dyUnconsumed, int @Nullable [] offsetInWindow, int type,
522                 int @NonNull [] consumed) {
523         }
524 
525         @Override
setNestedScrollingEnabled(boolean enabled)526         public void setNestedScrollingEnabled(boolean enabled) {
527 
528         }
529 
530         @Override
isNestedScrollingEnabled()531         public boolean isNestedScrollingEnabled() {
532             return false;
533         }
534 
535         @Override
startNestedScroll(int axes)536         public boolean startNestedScroll(int axes) {
537             return false;
538         }
539 
540         @Override
stopNestedScroll()541         public void stopNestedScroll() {
542 
543         }
544 
545         @Override
hasNestedScrollingParent()546         public boolean hasNestedScrollingParent() {
547             return false;
548         }
549 
550         @Override
dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)551         public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
552                 int dyUnconsumed, int[] offsetInWindow) {
553             return false;
554         }
555 
556         @Override
dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow)557         public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,
558                 int[] offsetInWindow) {
559             return false;
560         }
561 
562         @Override
dispatchNestedFling(float velocityX, float velocityY, boolean consumed)563         public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
564             return false;
565         }
566 
567         @Override
dispatchNestedPreFling(float velocityX, float velocityY)568         public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
569             return false;
570         }
571 
572         @Override
onStartNestedScroll(@onNull View child, @NonNull View target, int axes)573         public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
574             return false;
575         }
576 
577         @Override
onNestedScrollAccepted(@onNull View child, @NonNull View target, int axes)578         public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
579 
580         }
581 
582         @Override
onStopNestedScroll(@onNull View target)583         public void onStopNestedScroll(@NonNull View target) {
584 
585         }
586 
587         @Override
onNestedScroll(@onNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)588         public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
589                 int dxUnconsumed, int dyUnconsumed) {
590 
591         }
592 
593         @Override
onNestedPreScroll( @onNull View target, int dx, int dy, int @NonNull [] consumed)594         public void onNestedPreScroll(
595                 @NonNull View target, int dx, int dy, int @NonNull [] consumed) {
596 
597         }
598 
599         @Override
onNestedFling(@onNull View target, float velocityX, float velocityY, boolean consumed)600         public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
601                 boolean consumed) {
602             return false;
603         }
604 
605         @Override
onNestedPreFling(@onNull View target, float velocityX, float velocityY)606         public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
607             return false;
608         }
609 
610         @Override
getNestedScrollAxes()611         public int getNestedScrollAxes() {
612             return 0;
613         }
614     }
615 
616     private class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
617 
618         private Context mContext;
619         private int mOrientationSize;
620         private int mItemCount;
621         private boolean mVertical;
622 
TestAdapter(Context context, float orientationSize, int itemCount, boolean vertical)623         TestAdapter(Context context, float orientationSize, int itemCount, boolean vertical) {
624             mContext = context;
625             mOrientationSize = (int) Math.floor(orientationSize / itemCount);
626             mItemCount = itemCount;
627             mVertical = vertical;
628         }
629 
630         @Override
onCreateViewHolder(@onNull ViewGroup parent, int viewType)631         public @NonNull TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
632             View view = new View(mContext);
633 
634             int width;
635             int height;
636             if (mVertical) {
637                 width = ViewGroup.LayoutParams.MATCH_PARENT;
638                 height = mOrientationSize;
639             } else {
640                 width = mOrientationSize;
641                 height = ViewGroup.LayoutParams.MATCH_PARENT;
642             }
643 
644             view.setLayoutParams(new ViewGroup.LayoutParams(width, height));
645             view.setMinimumHeight(mOrientationSize);
646             return new TestViewHolder(view);
647         }
648 
649         @Override
onBindViewHolder(@onNull TestViewHolder holder, int position)650         public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
651 
652         }
653 
654         @Override
getItemCount()655         public int getItemCount() {
656             return mItemCount;
657         }
658     }
659 
660     private class TestViewHolder extends RecyclerView.ViewHolder {
661 
TestViewHolder(View itemView)662         TestViewHolder(View itemView) {
663             super(itemView);
664         }
665     }
666 
667 }
668