Dispatch onMomentumScrollEnd after programmatic scrolling (#45187)

Summary:
in iOS on a scroll generated programatically, the `onMomentScrollEnd` is fired, though in case of android the same does not happen, this PR tries to implement the same behaviour for android as well, while diving through the code it seems we have two extra `onMomentumScrollEnd` events. Only one event should be fired.

**iOS Behaviour on Programmatic Scroll**

https://github.com/facebook/react-native/assets/72331432/fb8f16b1-4db6-49fe-83a1-a1c40bf49705

https://github.com/facebook/react-native/assets/72331432/9842f522-b616-4fb3-b197-40817f4aa9cb

**Android Behaviour on Programmatic Scroll**

https://github.com/facebook/react-native/assets/72331432/c24d3f06-4e2a-4bef-81af-d9227a3b1a4a

https://github.com/facebook/react-native/assets/72331432/d4917843-730b-4bd7-90d9-33efb0f471a7

If closely observed we can see the `onMomentumScrollEnd` does not gets called in Android unlike to iOS.

## Changelog:

[Android][Fixed] - Dispatch onMomentumScrollEnd after programmatic scrolling

Pull Request resolved: https://github.com/facebook/react-native/pull/45187

Test Plan:
i have added updates to the FlatList example and ScrollViewSimple
here is a ScreenRecording of `onMomentumScrollEnd` firing in android after the code changes

https://github.com/facebook/react-native/assets/72331432/f036d1a5-6ebf-47ba-becd-4db98a406b15

https://github.com/facebook/react-native/assets/72331432/8c788c39-3392-4822-99c5-6e320398714b

Reviewed By: javache

Differential Revision: D65539724

Pulled By: Abbondanzo

fbshipit-source-id: f3a5527ac5979f5ec0c6ae18d80fdc20c9c9c14b
This commit is contained in:
BIKI DAS 2024-11-12 10:37:16 -08:00 committed by Facebook GitHub Bot
parent 6f59627903
commit c69e330324
4 changed files with 48 additions and 6 deletions

View File

@ -6975,6 +6975,7 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper {
public static final field SNAP_ALIGNMENT_START I
public static final fun addLayoutChangeListener (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$LayoutChangeListener;)V
public static final fun addScrollListener (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ScrollListener;)V
public static final fun dispatchMomentumEndOnAnimationEnd (Landroid/view/ViewGroup;)V
public static final fun emitLayoutChangeEvent (Landroid/view/ViewGroup;)V
public static final fun emitLayoutEvent (Landroid/view/ViewGroup;)V
public static final fun emitScrollBeginDragEvent (Landroid/view/ViewGroup;)V

View File

@ -1516,12 +1516,20 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
DEFAULT_FLING_ANIMATOR.cancel();
// Update the fling animator with new values
DEFAULT_FLING_ANIMATOR
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
.setIntValues(start, end);
int duration = ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext());
DEFAULT_FLING_ANIMATOR.setDuration(duration).setIntValues(start, end);
// Start the animator
DEFAULT_FLING_ANIMATOR.start();
if (mSendMomentumEvents) {
int xVelocity = 0;
if (duration > 0) {
xVelocity = (end - start) / duration;
}
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this, xVelocity, 0);
ReactScrollViewHelper.dispatchMomentumEndOnAnimationEnd(this);
}
}
@Override

View File

@ -1329,12 +1329,20 @@ public class ReactScrollView extends ScrollView
DEFAULT_FLING_ANIMATOR.cancel();
// Update the fling animator with new values
DEFAULT_FLING_ANIMATOR
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
.setIntValues(start, end);
int duration = ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext());
DEFAULT_FLING_ANIMATOR.setDuration(duration).setIntValues(start, end);
// Start the animator
DEFAULT_FLING_ANIMATOR.start();
if (mSendMomentumEvents) {
int yVelocity = 0;
if (duration > 0) {
yVelocity = (end - start) / duration;
}
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this, 0, yVelocity);
ReactScrollViewHelper.dispatchMomentumEndOnAnimationEnd(this);
}
}
@NonNull

View File

@ -409,6 +409,31 @@ public object ReactScrollViewHelper {
})
}
@JvmStatic
public fun <T> dispatchMomentumEndOnAnimationEnd(scrollView: T) where
T : HasFlingAnimator?,
T : HasScrollEventThrottle?,
T : ViewGroup {
scrollView
.getFlingAnimator()
.addListener(
object : Animator.AnimatorListener {
override fun onAnimationStart(animator: Animator) = Unit
override fun onAnimationEnd(animator: Animator) {
emitScrollMomentumEndEvent(scrollView)
animator.removeListener(this)
}
override fun onAnimationCancel(animator: Animator) {
emitScrollMomentumEndEvent(scrollView)
animator.removeListener(this)
}
override fun onAnimationRepeat(animator: Animator) = Unit
})
}
@JvmStatic
public fun <T> predictFinalScrollPosition(
scrollView: T,