1313import android .graphics .Rect ;
1414import android .graphics .drawable .ColorDrawable ;
1515import android .graphics .drawable .Drawable ;
16- import android . support . v4 .widget .NestedScrollView ;
17- import android . support . v4 .view .ViewCompat ;
16+ import androidx . core .widget .NestedScrollView ;
17+ import androidx . core .view .ViewCompat ;
1818import android .util .Log ;
1919import android .view .MotionEvent ;
2020import android .view .View ;
@@ -61,9 +61,7 @@ public class ReactNestedScrollView extends NestedScrollView implements ReactClip
6161 private boolean mActivelyScrolling ;
6262 private @ Nullable Rect mClippingRect ;
6363 private @ Nullable String mOverflow = ViewProps .HIDDEN ;
64- private boolean mDoneFlinging ;
6564 private boolean mDragging ;
66- private boolean mFlinging ;
6765 private boolean mPagingEnabled = false ;
6866 private @ Nullable Runnable mPostTouchRunnable ;
6967 private boolean mRemoveClippedSubviews ;
@@ -76,6 +74,8 @@ public class ReactNestedScrollView extends NestedScrollView implements ReactClip
7674 private int mSnapInterval = 0 ;
7775 private float mDecelerationRate = 0.985f ;
7876 private @ Nullable List <Integer > mSnapOffsets ;
77+ private boolean mSnapToStart = true ;
78+ private boolean mSnapToEnd = true ;
7979 private View mContentView ;
8080 private ReactViewBackgroundManager mReactBackgroundManager ;
8181
@@ -164,6 +164,13 @@ public void setSnapOffsets(List<Integer> snapOffsets) {
164164 mSnapOffsets = snapOffsets ;
165165 }
166166
167+ public void setSnapToStart (boolean snapToStart ) {
168+ mSnapToStart = snapToStart ;
169+ }
170+ public void setSnapToEnd (boolean snapToEnd ) {
171+ mSnapToEnd = snapToEnd ;
172+ }
173+
167174 public void flashScrollIndicators () {
168175 awakenScrollBars ();
169176 }
@@ -307,8 +314,21 @@ public void getClippingRect(Rect outClippingRect) {
307314
308315 @ Override
309316 public void fling (int velocityY ) {
317+ // Workaround.
318+ // On Android P if a ScrollView is inverted, we will get a wrong sign for
319+ // velocityY (see https://issuetracker.google.com/issues/112385925).
320+ // At the same time, mOnScrollDispatchHelper tracks the correct velocity direction.
321+ //
322+ // Hence, we can use the absolute value from whatever the OS gives
323+ // us and use the sign of what mOnScrollDispatchHelper has tracked.
324+ float signum = Math .signum (mOnScrollDispatchHelper .getYFlingVelocity ());
325+ if (signum == 0 ) {
326+ signum = Math .signum (velocityY );
327+ }
328+ final int correctedVelocityY = (int )(Math .abs (velocityY ) * signum );
329+
310330 if (mPagingEnabled ) {
311- flingAndSnap (velocityY );
331+ flingAndSnap (correctedVelocityY );
312332 } else if (mScroller != null ) {
313333 // FB SCROLLVIEW CHANGE
314334
@@ -324,7 +344,7 @@ public void fling(int velocityY) {
324344 getScrollX (), // startX
325345 getScrollY (), // startY
326346 0 , // velocityX
327- velocityY , // velocityY
347+ correctedVelocityY , // velocityY
328348 0 , // minX
329349 0 , // maxX
330350 0 , // minY
@@ -335,14 +355,14 @@ public void fling(int velocityY) {
335355
336356 ViewCompat .postInvalidateOnAnimation (this );
337357
338- // RNNestedScrollView CHANGE
358+ // RNNestedScrollView CHANGE (Should we use correctedVelocityY here as well?)
339359 // Fixed fling issue on support library 26 (see issue https://github.com/cesardeazevedo/react-native-nested-scroll-view/issues/16)
340360 super .fling (velocityY );
341361 // END FB SCROLLVIEW CHANGE
342362 } else {
343- super .fling (velocityY );
363+ super .fling (correctedVelocityY );
344364 }
345- handlePostTouchScrolling (0 , velocityY );
365+ handlePostTouchScrolling (0 , correctedVelocityY );
346366 }
347367
348368 private void enableFpsListener () {
@@ -575,10 +595,29 @@ private void flingAndSnap(int velocityY) {
575595 ? smallerOffset
576596 : largerOffset ;
577597
578- // Chose the correct snap offset based on velocity
579- if (velocityY > 0 ) {
598+ // if scrolling after the last snap offset and snapping to the
599+ // end of the list is disabled, then we allow free scrolling
600+ if (!mSnapToEnd && targetOffset >= lastOffset ) {
601+ if (getScrollY () >= lastOffset ) {
602+ // free scrolling
603+ } else {
604+ // snap to end
605+ targetOffset = lastOffset ;
606+ }
607+ } else if (!mSnapToStart && targetOffset <= firstOffset ) {
608+ if (getScrollY () <= firstOffset ) {
609+ // free scrolling
610+ } else {
611+ // snap to beginning
612+ targetOffset = firstOffset ;
613+ }
614+ } else if (velocityY > 0 ) {
615+ // when snapping velocity can feel sluggish for slow swipes
616+ velocityY += (int ) ((largerOffset - targetOffset ) * 10.0 );
580617 targetOffset = largerOffset ;
581618 } else if (velocityY < 0 ) {
619+ // when snapping velocity can feel sluggish for slow swipes
620+ velocityY -= (int ) ((targetOffset - smallerOffset ) * 10.0 );
582621 targetOffset = smallerOffset ;
583622 } else {
584623 targetOffset = nearestOffset ;
@@ -634,7 +673,7 @@ public void setEndFillColor(int color) {
634673
635674 @ Override
636675 protected void onOverScrolled (int scrollX , int scrollY , boolean clampedX , boolean clampedY ) {
637- if (mScroller != null ) {
676+ if (mScroller != null && mContentView != null ) {
638677 // FB SCROLLVIEW CHANGE
639678
640679 // This is part two of the reimplementation of fling to fix the bounce-back bug. See #fling() for
0 commit comments