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 8108106a271..10e1e443c95 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 @@ -23,11 +23,15 @@ import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.uimanager.MeasureSpecAssertions; +import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactClippingViewGroup; import com.facebook.react.uimanager.ReactClippingViewGroupHelper; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.events.NativeGestureUtil; import com.facebook.react.views.view.ReactViewBackgroundManager; @@ -48,6 +52,8 @@ public class ReactScrollView extends ScrollView private static @Nullable Field sScrollerField; private static boolean sTriedToGetScrollerField = false; + private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft"; + private static final String CONTENT_OFFSET_TOP = "contentOffsetTop"; private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper(); private final @Nullable OverScroller mScroller; @@ -74,6 +80,7 @@ public class ReactScrollView extends ScrollView private boolean mSnapToEnd = true; private View mContentView; private ReactViewBackgroundManager mReactBackgroundManager; + private @Nullable StateWrapper mStateWrapper; public ReactScrollView(ReactContext context) { this(context, null); @@ -287,6 +294,8 @@ public class ReactScrollView extends ScrollView mVelocityHelper.calculateVelocity(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_UP && mDragging) { + updateStateOnScroll(); + float velocityX = mVelocityHelper.getXVelocity(); float velocityY = mVelocityHelper.getYVelocity(); ReactScrollViewHelper.emitScrollEndDragEvent(this, velocityX, velocityY); @@ -488,6 +497,8 @@ public class ReactScrollView extends ScrollView ViewCompat.postOnAnimationDelayed( ReactScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); } else { + updateStateOnScroll(); + if (mPagingEnabled && !mSnappingToPage) { // Only if we have pagingEnabled and we have not snapped to the page do we // need to continue checking for the scroll. And we cause that scroll by asking for @@ -799,4 +810,23 @@ public class ReactScrollView extends ScrollView public void setBorderStyle(@Nullable String style) { mReactBackgroundManager.setBorderStyle(style); } + + public void updateState(@Nullable StateWrapper stateWrapper) { + mStateWrapper = stateWrapper; + } + + /** + * Called on any stabilized onScroll change to propagate content offset value to a Shadow Node. + */ + private void updateStateOnScroll() { + if (mStateWrapper == null) { + return; + } + + WritableMap map = new WritableNativeMap(); + map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(getScrollX())); + map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(getScrollY())); + + mStateWrapper.updateState(map); + } } 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 1c45db9880f..d1bb2e4070c 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 @@ -17,7 +17,9 @@ import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactClippingViewGroupHelper; +import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.Spacing; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewProps; @@ -290,6 +292,13 @@ public class ReactScrollViewManager extends ViewGroupManager } } + @Override + public Object updateState( + ReactScrollView view, ReactStylesDiffMap props, @Nullable StateWrapper stateWrapper) { + view.updateState(stateWrapper); + return null; + } + @Override public @Nullable Map getExportedCustomDirectEventTypeConstants() { return createExportedCustomDirectEventTypeConstants(); diff --git a/ReactCommon/fabric/components/scrollview/ScrollViewState.h b/ReactCommon/fabric/components/scrollview/ScrollViewState.h index 240ffbfba79..3a6023499e6 100644 --- a/ReactCommon/fabric/components/scrollview/ScrollViewState.h +++ b/ReactCommon/fabric/components/scrollview/ScrollViewState.h @@ -29,9 +29,14 @@ class ScrollViewState final { #ifdef ANDROID ScrollViewState() = default; - ScrollViewState(ScrollViewState const &previousState, folly::dynamic data){}; + ScrollViewState(ScrollViewState const &previousState, folly::dynamic data) + : contentOffset({(Float)data["contentOffsetLeft"].getDouble(), + (Float)data["contentOffsetTop"].getDouble()}), + contentBoundingRect({}){}; + folly::dynamic getDynamic() const { - return {}; + return folly::dynamic::object("contentOffsetLeft", contentOffset.x)( + "contentOffsetTop", contentOffset.y); }; #endif };