From 56e5dff73f302f2901183679105b09f0393e36fb Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Sat, 13 Sep 2025 21:41:49 -0700 Subject: [PATCH] Hysteresis window for VirtualViewExperimental (#53765) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53765 Changelog: [Internal] Introduce hysteresis window for experimental VirtualView Reviewed By: yungsters Differential Revision: D82354142 fbshipit-source-id: 13ec0e3a46930d3bea0ea67060c5f0e1c137dec1 --- .../views/scroll/VirtualViewContainer.kt | 31 ++++++++++++++----- .../ReactVirtualViewExperimental.kt | 1 + 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VirtualViewContainer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VirtualViewContainer.kt index 042bb378e90..a0212174518 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VirtualViewContainer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VirtualViewContainer.kt @@ -50,11 +50,13 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean { internal class VirtualViewContainerState { private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio() + private val hysteresisRatio: Double = ReactNativeFeatureFlags.virtualViewHysteresisRatio() private val virtualViews: MutableSet = mutableSetOf() private val emptyRect: Rect = Rect() private val visibleRect: Rect = Rect() private val prerenderRect: Rect = Rect() + private val hysteresisRect: Rect = Rect() private val onWindowFocusChangeListener = if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) { ViewTreeObserver.OnWindowFocusChangeListener { @@ -124,7 +126,7 @@ internal class VirtualViewContainerState { virtualViewsIt.forEach { vv -> val rect = vv.containerRelativeRect - var mode = VirtualViewMode.Hidden + var mode: VirtualViewMode? = VirtualViewMode.Hidden var thresholdRect = emptyRect when { rectsOverlap(rect, visibleRect) -> { @@ -143,14 +145,29 @@ internal class VirtualViewContainerState { mode = VirtualViewMode.Prerender thresholdRect = prerenderRect } - else -> {} + else -> { + if (hysteresisRatio > 0.0) { + hysteresisRect.set(prerenderRect) + hysteresisRect.inset( + (-visibleRect.width() * hysteresisRatio).toInt(), + (-visibleRect.height() * hysteresisRatio).toInt(), + ) + if (rectsOverlap(rect, hysteresisRect)) { + mode = null + } + } + } } - debugLog( - "updateModes", - { "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" }, - ) - vv.onModeChange(mode, thresholdRect) + if (mode != null) { + vv.onModeChange(mode, thresholdRect) + debugLog( + "updateModes", + { + "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" + }, + ) + } } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental.kt index 06b1078eea5..865d5127cf1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental.kt @@ -20,6 +20,7 @@ import com.facebook.react.uimanager.ReactClippingViewGroup import com.facebook.react.uimanager.ReactRoot import com.facebook.react.views.scroll.VirtualView import com.facebook.react.views.scroll.VirtualViewContainer +import com.facebook.react.views.scroll.debugLog import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.virtual.VirtualViewMode import com.facebook.react.views.virtual.VirtualViewModeChangeEmitter