From cf55fd587e6cc82a73079be6076d244ab72fa359 Mon Sep 17 00:00:00 2001 From: Xin Chen Date: Tue, 19 Apr 2022 20:28:17 -0700 Subject: [PATCH] Add scrollEventThrottle prop support in Android Summary: This diff adds `scrollEventThrottle` prop to Android platform. See [public doc](https://reactnative.dev/docs/scrollview#scrolleventthrottle-ios) for this prop. Currently this is only supported in iOS. The throttling logic is [following](https://github.com/facebook/react-native/blob/main/React/Views/ScrollView/RCTScrollView.m#L650) iOS existing one. Changelog: [Android][Added] - Add scrollEventThrottle prop support in Android Reviewed By: mdvacca Differential Revision: D35735978 fbshipit-source-id: 97b73b4d5fbb93696555917b9252e95fd79ca581 --- .../react/config/ReactFeatureFlags.java | 3 + .../scroll/ReactHorizontalScrollView.java | 26 ++++++++- .../ReactHorizontalScrollViewManager.java | 5 ++ .../react/views/scroll/ReactScrollView.java | 26 ++++++++- .../views/scroll/ReactScrollViewHelper.java | 56 +++++++++++++++---- .../views/scroll/ReactScrollViewManager.java | 5 ++ 6 files changed, 108 insertions(+), 13 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index b0cd238d0f1..cbf7388d472 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -114,4 +114,7 @@ public class ReactFeatureFlags { public static boolean enableSpannableCache = false; public static boolean dispatchPointerEvents = false; + + /** Feature Flag to control RN Android scrollEventThrottle prop. */ + public static boolean enableScrollEventThrottle = false; } 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 83f14c50c4c..d40b2584341 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 @@ -43,6 +43,7 @@ import com.facebook.react.uimanager.ReactOverflowViewWithInset; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.events.NativeGestureUtil; import com.facebook.react.views.scroll.ReactScrollViewHelper.HasFlingAnimator; +import com.facebook.react.views.scroll.ReactScrollViewHelper.HasScrollEventThrottle; import com.facebook.react.views.scroll.ReactScrollViewHelper.HasScrollState; import com.facebook.react.views.scroll.ReactScrollViewHelper.ReactScrollViewScrollState; import com.facebook.react.views.view.ReactViewBackgroundManager; @@ -56,7 +57,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView FabricViewStateManager.HasFabricViewStateManager, ReactOverflowViewWithInset, HasScrollState, - HasFlingAnimator { + HasFlingAnimator, + HasScrollEventThrottle { private static boolean DEBUG_MODE = false && ReactBuildConfig.DEBUG; private static String TAG = ReactHorizontalScrollView.class.getSimpleName(); @@ -103,6 +105,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private final ReactScrollViewScrollState mReactScrollViewScrollState; private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollX", 0, 0); private PointerEvents mPointerEvents = PointerEvents.AUTO; + private long mLastScrollDispatchTime = 0; + private int mScrollEventThrottle = 0; private final Rect mTempRect = new Rect(); @@ -1272,4 +1276,24 @@ public class ReactHorizontalScrollView extends HorizontalScrollView public PointerEvents getPointerEvents() { return mPointerEvents; } + + @Override + public void setScrollEventThrottle(int scrollEventThrottle) { + mScrollEventThrottle = scrollEventThrottle; + } + + @Override + public int getScrollEventThrottle() { + return mScrollEventThrottle; + } + + @Override + public void setLastScrollDispatchTime(long lastScrollDispatchTime) { + mLastScrollDispatchTime = lastScrollDispatchTime; + } + + @Override + public long getLastScrollDispatchTime() { + return mLastScrollDispatchTime; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java index 2cdfec69aa1..b77c73bdcb0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java @@ -325,4 +325,9 @@ public class ReactHorizontalScrollViewManager extends ViewGroupManager void emitScrollEvent( + T scrollView, float xVelocity, float yVelocity) { emitScrollEvent(scrollView, ScrollEventType.SCROLL, xVelocity, yVelocity); } - public static void emitScrollBeginDragEvent(ViewGroup scrollView) { + public static void emitScrollBeginDragEvent( + T scrollView) { emitScrollEvent(scrollView, ScrollEventType.BEGIN_DRAG); } - public static void emitScrollEndDragEvent( - ViewGroup scrollView, float xVelocity, float yVelocity) { + public static void emitScrollEndDragEvent( + T scrollView, float xVelocity, float yVelocity) { emitScrollEvent(scrollView, ScrollEventType.END_DRAG, xVelocity, yVelocity); } - public static void emitScrollMomentumBeginEvent( - ViewGroup scrollView, int xVelocity, int yVelocity) { + public static void emitScrollMomentumBeginEvent( + T scrollView, int xVelocity, int yVelocity) { emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_BEGIN, xVelocity, yVelocity); } - public static void emitScrollMomentumEndEvent(ViewGroup scrollView) { + public static void emitScrollMomentumEndEvent( + T scrollView) { emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_END); } - private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scrollEventType) { + private static void emitScrollEvent( + T scrollView, ScrollEventType scrollEventType) { emitScrollEvent(scrollView, scrollEventType, 0, 0); } - private static void emitScrollEvent( - ViewGroup scrollView, ScrollEventType scrollEventType, float xVelocity, float yVelocity) { + private static void emitScrollEvent( + T scrollView, ScrollEventType scrollEventType, float xVelocity, float yVelocity) { + long now = System.currentTimeMillis(); + // Throttle the scroll event if scrollEventThrottle is set to be equal or more than 17 ms. + // We limit the delta to 17ms so that small throttles intended to enable 60fps updates will not + // inadvertently filter out any scroll events. + if (ReactFeatureFlags.enableScrollEventThrottle + && scrollView.getScrollEventThrottle() + >= Math.max(17, now - scrollView.getLastScrollDispatchTime())) { + // Scroll events are throttled. + return; + } + View contentView = scrollView.getChildAt(0); if (contentView == null) { @@ -129,6 +145,7 @@ public class ReactScrollViewHelper { contentView.getHeight(), scrollView.getWidth(), scrollView.getHeight())); + scrollView.setLastScrollDispatchTime(now); } } @@ -469,7 +486,7 @@ public class ReactScrollViewHelper { public static < T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState - & HasFlingAnimator> + & HasFlingAnimator & HasScrollEventThrottle> void updateStateOnScrollChanged( final T scrollView, final float xVelocity, final float yVelocity) { // Race an UpdateState with every onScroll. This makes it more likely that, in Fabric, @@ -579,4 +596,21 @@ public class ReactScrollViewHelper { /** Get the fling distance with current velocity for prediction */ int getFlingExtrapolatedDistance(int velocity); } + + public interface HasScrollEventThrottle { + /** + * Set the scroll event throttle in ms. This number is used to throttle the scroll events. The + * default value is zero, which means the scroll events are sent with no throttle. + */ + void setScrollEventThrottle(int scrollEventThrottle); + + /** Get the scroll event throttle in ms. */ + int getScrollEventThrottle(); + + /** Set the scroll view's last dispatch time for throttling */ + void setLastScrollDispatchTime(long lastScrollDispatchTime); + + /** Get the scroll view dispatch time for throttling */ + long getLastScrollDispatchTime(); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java index d1b32c61d3c..1f496220248 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -367,4 +367,9 @@ public class ReactScrollViewManager extends ViewGroupManager public void setPointerEvents(ReactScrollView view, @Nullable String pointerEventsStr) { view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr)); } + + @ReactProp(name = "scrollEventThrottle") + public void setScrollEventThrottle(ReactScrollView view, int scrollEventThrottle) { + view.setScrollEventThrottle(scrollEventThrottle); + } }