diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index dcc64670f7cced..100e70853ad05a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -93,7 +93,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private int mEndFillColor = Color.TRANSPARENT; private boolean mDisableIntervalMomentum = false; private int mSnapInterval = 0; - private float mDecelerationRate = 0.985f; private @Nullable List mSnapOffsets; private boolean mSnapToStart = true; private boolean mSnapToEnd = true; @@ -216,10 +215,10 @@ public void setPagingEnabled(boolean pagingEnabled) { } public void setDecelerationRate(float decelerationRate) { - mDecelerationRate = decelerationRate; + getReactScrollViewScrollState().setDecelerationRate(decelerationRate); if (mScroller != null) { - mScroller.setFriction(1.0f - mDecelerationRate); + mScroller.setFriction(1.0f - decelerationRate); } } @@ -802,32 +801,9 @@ public void run() { } private int predictFinalScrollPosition(int velocityX) { - // ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's - // no way to customize the scroll duration. So, we create a temporary OverScroller - // so we can predict where a fling would land and snap to nearby that point. - OverScroller scroller = new OverScroller(getContext()); - scroller.setFriction(1.0f - mDecelerationRate); - // predict where a fling would end up so we can scroll to the nearest snap offset - int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); - int width = getWidth() - ViewCompat.getPaddingStart(this) - ViewCompat.getPaddingEnd(this); - scroller.fling( - ReactScrollViewHelper.getNextFlingStartValue( - this, - getScrollX(), - getReactScrollViewScrollState().getFinalAnimatedPositionScroll().x, - velocityX), // startX - getScrollY(), // startY - velocityX, // velocityX - 0, // velocityY - 0, // minX - maximumOffset, // maxX - 0, // minY - 0, // maxY - width / 2, // overX - 0 // overY - ); - return scroller.getFinalX(); + final int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); + return ReactScrollViewHelper.predictFinalScrollPosition(this, velocityX, 0, maximumOffset, 0).x; } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 755f54420c0a22..89bdbf47e1c555 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -89,7 +89,6 @@ public class ReactScrollView extends ScrollView private int mEndFillColor = Color.TRANSPARENT; private boolean mDisableIntervalMomentum = false; private int mSnapInterval = 0; - private float mDecelerationRate = 0.985f; private @Nullable List mSnapOffsets; private boolean mSnapToStart = true; private boolean mSnapToEnd = true; @@ -191,10 +190,10 @@ public void setPagingEnabled(boolean pagingEnabled) { } public void setDecelerationRate(float decelerationRate) { - mDecelerationRate = decelerationRate; + getReactScrollViewScrollState().setDecelerationRate(decelerationRate); if (mScroller != null) { - mScroller.setFriction(1.0f - mDecelerationRate); + mScroller.setFriction(1.0f - decelerationRate); } } @@ -591,32 +590,9 @@ public void run() { } private int predictFinalScrollPosition(int velocityY) { - // ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's - // no way to customize the scroll duration. So, we create a temporary OverScroller - // so we can predict where a fling would land and snap to nearby that point. - OverScroller scroller = new OverScroller(getContext()); - scroller.setFriction(1.0f - mDecelerationRate); - // predict where a fling would end up so we can scroll to the nearest snap offset - int maximumOffset = getMaxScrollY(); - int height = getHeight() - getPaddingBottom() - getPaddingTop(); - scroller.fling( - getScrollX(), // startX - ReactScrollViewHelper.getNextFlingStartValue( - this, - getScrollY(), - getReactScrollViewScrollState().getFinalAnimatedPositionScroll().y, - velocityY), // startY - 0, // velocityX - velocityY, // velocityY - 0, // minX - 0, // maxX - 0, // minY - maximumOffset, // maxY - 0, // overX - height / 2 // overY - ); - return scroller.getFinalY(); + return ReactScrollViewHelper.predictFinalScrollPosition(this, 0, velocityY, 0, getMaxScrollY()) + .y; } private View getContentView() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index 6d76c670452171..067d844f92efe8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -15,6 +15,7 @@ import android.view.ViewGroup; import android.widget.OverScroller; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; import com.facebook.common.logging.FLog; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; @@ -222,6 +223,7 @@ public static class ReactScrollViewScrollState { private final Point mLastStateUpdateScroll = new Point(-1, -1); private boolean mIsCanceled = false; private boolean mIsFinished = true; + private float mDecelerationRate = 0.985f; public ReactScrollViewScrollState(final int layoutDirection) { mLayoutDirection = layoutDirection; @@ -291,6 +293,17 @@ public ReactScrollViewScrollState setIsFinished(boolean isFinished) { mIsFinished = isFinished; return this; } + + /** Get true if previous animation was finished */ + public float getDecelerationRate() { + return mDecelerationRate; + } + + /** Set the state of current animation is finished or not */ + public ReactScrollViewScrollState setDecelerationRate(float decelerationRate) { + mDecelerationRate = decelerationRate; + return this; + } } /** @@ -491,6 +504,54 @@ public void onAnimationRepeat(Animator animator) {} }); } + public static < + T extends + ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState + & HasFlingAnimator> + Point predictFinalScrollPosition( + final T scrollView, + int velocityX, + int velocityY, + int maximumOffsetX, + int maximumOffsetY) { + final ReactScrollViewScrollState scrollState = scrollView.getReactScrollViewScrollState(); + // ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's + // no way to customize the scroll duration. So, we create a temporary OverScroller + // so we can predict where a fling would land and snap to nearby that point. + OverScroller scroller = new OverScroller(scrollView.getContext()); + scroller.setFriction(1.0f - scrollState.getDecelerationRate()); + + // predict where a fling would end up so we can scroll to the nearest snap offset + int width = + scrollView.getWidth() + - ViewCompat.getPaddingStart(scrollView) + - ViewCompat.getPaddingEnd(scrollView); + int height = + scrollView.getHeight() - scrollView.getPaddingBottom() - scrollView.getPaddingTop(); + Point finalAnimatedPositionScroll = scrollState.getFinalAnimatedPositionScroll(); + scroller.fling( + getNextFlingStartValue( + scrollView, + scrollView.getScrollX(), + finalAnimatedPositionScroll.x, + velocityX), // startX + getNextFlingStartValue( + scrollView, + scrollView.getScrollY(), + finalAnimatedPositionScroll.y, + velocityY), // startY + velocityX, // velocityX + velocityY, // velocityY + 0, // minX + maximumOffsetX, // maxX + 0, // minY + maximumOffsetY, // maxY + width / 2, // overX + height / 2 // overY + ); + return new Point(scroller.getFinalX(), scroller.getFinalY()); + } + public interface HasScrollState { /** Get the scroll state for the current ScrollView */ ReactScrollViewScrollState getReactScrollViewScrollState();