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 100e70853ad05a..d033e0687122cc 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 void run() { 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 void startFlingAnimator(int start, int end) { 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 89bdbf47e1c555..652ec951301a1a 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 void run() { 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 void startFlingAnimator(int start, int end) { 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 067d844f92efe8..e9d1e59fa2297f 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 void onAnimationRepeat(Animator animator) {} & 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 interface HasFlingAnimator { /** 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); } }