From 53f89bacd471e156abe2b4d1ef8ea8b02f37f9f6 Mon Sep 17 00:00:00 2001 From: Calix Tang Date: Tue, 23 Sep 2025 16:06:40 -0700 Subject: [PATCH] VirtualView v2 support in HorizontalScrollView native component (Android) (#53890) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53890 Changelog: [Internal] - Adds support for VirtualView v2 in Android HorizontalScrollView native component by implementing VirtualViewContainer interface. Only on Android because iOS equivalent native components are already set up to support virtualview v2 on horizontal scrollviews. ## Changes in Detail Adds necessary changes to Android `HorizontalScrollView` native component to support the experimental version of VirtualView. Reviewed By: lunaleaps Differential Revision: D82783403 fbshipit-source-id: 1e55eb054ab098af5ca8dd526b27c574025943c4 --- .../ReactAndroid/api/ReactAndroid.api | 3 ++- .../scroll/ReactHorizontalScrollView.java | 27 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 718e2c66831..87d9afd5774 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -5566,7 +5566,7 @@ public final class com/facebook/react/views/scroll/ReactHorizontalScrollContaine public final class com/facebook/react/views/scroll/ReactHorizontalScrollContainerViewManager$Companion { } -public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android/widget/HorizontalScrollView, android/view/View$OnLayoutChangeListener, android/view/ViewGroup$OnHierarchyChangeListener, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/views/scroll/ReactAccessibleScrollView, com/facebook/react/views/scroll/ReactScrollViewHelper$HasFlingAnimator, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollEventThrottle, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollState, com/facebook/react/views/scroll/ReactScrollViewHelper$HasSmoothScroll, com/facebook/react/views/scroll/ReactScrollViewHelper$HasStateWrapper { +public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android/widget/HorizontalScrollView, android/view/View$OnLayoutChangeListener, android/view/ViewGroup$OnHierarchyChangeListener, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/views/scroll/ReactAccessibleScrollView, com/facebook/react/views/scroll/ReactScrollViewHelper$HasFlingAnimator, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollEventThrottle, com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollState, com/facebook/react/views/scroll/ReactScrollViewHelper$HasSmoothScroll, com/facebook/react/views/scroll/ReactScrollViewHelper$HasStateWrapper, com/facebook/react/views/scroll/VirtualViewContainer { public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Lcom/facebook/react/views/scroll/FpsListener;)V public fun abortAnimation ()V @@ -5596,6 +5596,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android public fun getScrollEnabled ()Z public fun getScrollEventThrottle ()I public fun getStateWrapper ()Lcom/facebook/react/uimanager/StateWrapper; + public fun getVirtualViewContainerState ()Lcom/facebook/react/views/scroll/VirtualViewContainerState; protected fun handleInterceptedTouchEvent (Landroid/view/MotionEvent;)V public fun isPartiallyScrolledInView (Landroid/view/View;)Z protected fun onAttachedToWindow ()V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index c9aac09ba92..63491c9d1c5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -79,7 +79,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView HasStateWrapper, HasFlingAnimator, HasScrollEventThrottle, - HasSmoothScroll { + HasSmoothScroll, + VirtualViewContainer { private static boolean DEBUG_MODE = false && ReactBuildConfig.DEBUG; private static String TAG = ReactHorizontalScrollView.class.getSimpleName(); @@ -100,6 +101,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollX", 0, 0); private Rect mOverflowInset = new Rect(); + private @Nullable VirtualViewContainerState mVirtualViewContainerState; private boolean mActivelyScrolling; private @Nullable Rect mClippingRect; private Overflow mOverflow = Overflow.SCROLL; @@ -156,6 +158,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView */ private void initView() { mOverflowInset = new Rect(); + mVirtualViewContainerState = null; mActivelyScrolling = false; mClippingRect = null; // The default value for `overflow` is set to `Visible` in the Yoga style props. @@ -209,6 +212,15 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private void updateView() {} + @Override + public VirtualViewContainerState getVirtualViewContainerState() { + if (mVirtualViewContainerState == null) { + mVirtualViewContainerState = new VirtualViewContainerState(this); + } + + return mVirtualViewContainerState; + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); @@ -512,6 +524,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView } ReactScrollViewHelper.emitLayoutEvent(this); + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } /** @@ -605,7 +620,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView this, mOnScrollDispatchHelper.getXFlingVelocity(), mOnScrollDispatchHelper.getYFlingVelocity()); + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } + } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); } @@ -856,6 +875,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView if (mRemoveClippedSubviews) { updateClippingRect(); } + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } @Override @@ -875,6 +897,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView if (mMaintainVisibleContentPositionHelper != null) { mMaintainVisibleContentPositionHelper.stop(); } + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.cleanup(); + } } @Override