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 b0cd238d0f13eb..cbf7388d472805 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 static boolean doesUseOverflowInset() { 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 83f14c50c4c104..d40b2584341c85 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.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 void setPointerEvents(PointerEvents pointerEvents) { 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 2cdfec69aa1ee8..b77c73bdcb0351 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 void setContentOffset(ReactHorizontalScrollView view, ReadableMap value) public void setPointerEvents(ReactHorizontalScrollView view, @Nullable String pointerEventsStr) { view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr)); } + + @ReactProp(name = "scrollEventThrottle") + public void setScrollEventThrottle(ReactHorizontalScrollView view, int scrollEventThrottle) { + view.setScrollEventThrottle(scrollEventThrottle); + } } 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 d33c08f1fdbc72..b00bc7c7931d50 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 @@ -42,6 +42,7 @@ 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; @@ -62,7 +63,8 @@ public class ReactScrollView extends ScrollView FabricViewStateManager.HasFabricViewStateManager, ReactOverflowViewWithInset, HasScrollState, - HasFlingAnimator { + HasFlingAnimator, + HasScrollEventThrottle { private static @Nullable Field sScrollerField; private static boolean sTriedToGetScrollerField = false; @@ -104,6 +106,8 @@ public class ReactScrollView extends ScrollView new ReactScrollViewScrollState(ViewCompat.LAYOUT_DIRECTION_LTR); private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollY", 0, 0); private PointerEvents mPointerEvents = PointerEvents.AUTO; + private long mLastScrollDispatchTime = 0; + private int mScrollEventThrottle = 0; public ReactScrollView(Context context) { this(context, null); @@ -1168,4 +1172,24 @@ public void setPointerEvents(PointerEvents pointerEvents) { 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/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index c8ae52828c76ef..6e7ad8cc33459b 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 @@ -22,6 +22,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.build.ReactBuildConfig; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.uimanager.FabricViewStateManager; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.UIManagerHelper; @@ -68,34 +69,49 @@ void onScroll( private static boolean mSmoothScrollDurationInitialized = false; /** Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}. */ - public static void emitScrollEvent(ViewGroup scrollView, float xVelocity, float yVelocity) { + public static 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 @@ private static void emitScrollEvent( contentView.getHeight(), scrollView.getWidth(), scrollView.getHeight())); + scrollView.setLastScrollDispatchTime(now); } } @@ -469,7 +486,7 @@ public WritableMap getStateUpdate() { 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 interface HasFlingAnimator { /** 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 d1b32c61d3c96c..1f496220248228 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 static Map createExportedCustomDirectEventTypeConstants() 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); + } }