From ed048eb112f8122af47808c4f3843e0b07b1f8ed Mon Sep 17 00:00:00 2001 From: Calix Tang Date: Tue, 23 Sep 2025 13:13:38 -0700 Subject: [PATCH] VirtualView v2 support in HorizontalScrollView native component (Android) (#53890) Summary: 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 --- .../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 718e2c66831ee6..87d9afd5774d21 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 c9aac09ba92a41..63491c9d1c508b 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 ReactHorizontalScrollView(Context context, @Nullable FpsListener fpsListe */ 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 @@ private void initView() { 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 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } ReactScrollViewHelper.emitLayoutEvent(this); + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } /** @@ -605,7 +620,11 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) { this, mOnScrollDispatchHelper.getXFlingVelocity(), mOnScrollDispatchHelper.getYFlingVelocity()); + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } + } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); } @@ -856,6 +875,9 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (mRemoveClippedSubviews) { updateClippingRect(); } + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.updateState(); + } } @Override @@ -875,6 +897,9 @@ protected void onDetachedFromWindow() { if (mMaintainVisibleContentPositionHelper != null) { mMaintainVisibleContentPositionHelper.stop(); } + if (mVirtualViewContainerState != null) { + mVirtualViewContainerState.cleanup(); + } } @Override