Skip to content

Commit

Permalink
Dispatch onMomentumScrollEnd after programmatic scrolling (#45187)
Browse files Browse the repository at this point in the history
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: #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
  • Loading branch information
Biki-das authored and facebook-github-bot committed Nov 12, 2024
1 parent 6f59627 commit c69e330
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1516,12 +1516,20 @@ public void startFlingAnimator(int start, int end) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1329,12 +1329,20 @@ public void startFlingAnimator(int start, int end) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit c69e330

Please sign in to comment.