From fe6277a30d3ec19e4772991e30ae20c3a9cfe565 Mon Sep 17 00:00:00 2001 From: Xin Chen Date: Mon, 6 Dec 2021 19:46:10 -0800 Subject: [PATCH] Support override predict final scroll position with custom fling animator Summary: This diff add custom prediction for fling distance support. This is needed for customize fling animator to calculate predicted fling distance, instead of using the overscroller that may not be used by the animator. More context on this -- when fling happens, our code will first predict the final fling position `p`, apply the snapping logic to decide the expected snapping position `pSnapping` given `p`, scroll velocity and children layout, then trigger the overscroller (existing) or custom fling animator to finish the fling. Currently, the prediction logic is done with overscroller, and custom fling animator has no control over how the predicted fling distance should be. Changes in this diff allow the animator to override `getExtrapolatedDistance` method and provide that information. Changelog: [Android][Added] - Add new API for custom fling animator to provide predicted travel distance for its fling animation. Reviewed By: mdvacca Differential Revision: D32571734 fbshipit-source-id: d34b969206f8b6cb5c68d2f50a18749bfebbc97e --- .../scroll/ReactHorizontalScrollView.java | 20 ++++++++++++++++++- .../react/views/scroll/ReactScrollView.java | 20 +++++++++++++++++-- .../views/scroll/ReactScrollViewHelper.java | 11 ++++++---- 3 files changed, 44 insertions(+), 7 deletions(-) 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 100e70853ad..d033e068712 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 @@ -803,7 +803,16 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private int predictFinalScrollPosition(int velocityX) { // predict where a fling would end up so we can scroll to the nearest snap offset final int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); - return ReactScrollViewHelper.predictFinalScrollPosition(this, velocityX, 0, maximumOffset, 0).x; + // TODO(T106335409): Existing prediction still uses overscroller. Consider change this to use + // fling animator instead. + return getFlingAnimator() == DEFAULT_FLING_ANIMATOR + ? ReactScrollViewHelper.predictFinalScrollPosition(this, velocityX, 0, maximumOffset, 0).x + : ReactScrollViewHelper.getNextFlingStartValue( + this, + getScrollX(), + getReactScrollViewScrollState().getFinalAnimatedPositionScroll().x, + velocityX) + + getFlingExtrapolatedDistance(velocityX); } /** @@ -1224,4 +1233,13 @@ public class ReactHorizontalScrollView extends HorizontalScrollView public ValueAnimator getFlingAnimator() { return DEFAULT_FLING_ANIMATOR; } + + @Override + public int getFlingExtrapolatedDistance(int velocityX) { + // The DEFAULT_FLING_ANIMATOR uses AccelerateDecelerateInterpolator, which is not depending on + // the init velocity. We use the overscroller to decide the fling distance. + return ReactScrollViewHelper.predictFinalScrollPosition( + this, velocityX, 0, Math.max(0, computeHorizontalScrollRange() - getWidth()), 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 89bdbf47e1c..652ec951301 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 @@ -591,8 +591,16 @@ public class ReactScrollView extends ScrollView private int predictFinalScrollPosition(int velocityY) { // predict where a fling would end up so we can scroll to the nearest snap offset - return ReactScrollViewHelper.predictFinalScrollPosition(this, 0, velocityY, 0, getMaxScrollY()) - .y; + // TODO(T106335409): Existing prediction still uses overscroller. Consider change this to use + // fling animator instead. + return getFlingAnimator() == DEFAULT_FLING_ANIMATOR + ? ReactScrollViewHelper.predictFinalScrollPosition(this, 0, velocityY, 0, getMaxScrollY()).y + : ReactScrollViewHelper.getNextFlingStartValue( + this, + getScrollY(), + getReactScrollViewScrollState().getFinalAnimatedPositionScroll().y, + velocityY) + + getFlingExtrapolatedDistance(velocityY); } private View getContentView() { @@ -1088,4 +1096,12 @@ public class ReactScrollView extends ScrollView public ValueAnimator getFlingAnimator() { return DEFAULT_FLING_ANIMATOR; } + + @Override + public int getFlingExtrapolatedDistance(int velocityY) { + // The DEFAULT_FLING_ANIMATOR uses AccelerateDecelerateInterpolator, which is not depending on + // the init velocity. We use the overscroller to decide the fling distance. + return ReactScrollViewHelper.predictFinalScrollPosition(this, 0, velocityY, 0, getMaxScrollY()) + .y; + } } 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 067d844f92e..e9d1e59fa22 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 @@ -510,10 +510,10 @@ public class ReactScrollViewHelper { & HasFlingAnimator> Point predictFinalScrollPosition( final T scrollView, - int velocityX, - int velocityY, - int maximumOffsetX, - int maximumOffsetY) { + final int velocityX, + final int velocityY, + final int maximumOffsetX, + final 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 @@ -566,5 +566,8 @@ public class ReactScrollViewHelper { /** Get the fling animator that is reused for the ScrollView to handle fling animation. */ ValueAnimator getFlingAnimator(); + + /** Get the fling distance with current velocity for prediction */ + int getFlingExtrapolatedDistance(int velocity); } }