@@ -22,7 +22,6 @@ internal interface VirtualViewContainer {
2222
2323public interface VirtualView {
2424 public val virtualViewID: String
25-
2625 public val containerRelativeRect: Rect
2726
2827 public fun onModeChange (newMode : VirtualViewMode , thresholdRect : Rect ): Unit
@@ -35,7 +34,7 @@ public interface VirtualView {
3534 * considered to overlap with another Rect if the line or point is within the rect bounds. However,
3635 * two Rects are not considered to overlap if they only share a boundary.
3736 */
38- private fun rectsOverlap (rect1 : Rect , rect2 : Rect ): Boolean {
37+ internal fun rectsOverlap (rect1 : Rect , rect2 : Rect ): Boolean {
3938 if (rect1.top >= rect2.bottom || rect2.top >= rect1.bottom) {
4039 // No overlap on the y-axis.
4140 return false
@@ -47,17 +46,15 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
4746 return true
4847}
4948
50- internal class VirtualViewContainerState {
51-
52- private val prerenderRatio: Double = ReactNativeFeatureFlags .virtualViewPrerenderRatio()
53- private val hysteresisRatio: Double = ReactNativeFeatureFlags .virtualViewHysteresisRatio()
54-
55- private val virtualViews: MutableSet <VirtualView > = mutableSetOf ()
56- private val emptyRect: Rect = Rect ()
57- private val visibleRect: Rect = Rect ()
58- private val prerenderRect: Rect = Rect ()
59- private val hysteresisRect: Rect = Rect ()
60- private val onWindowFocusChangeListener =
49+ internal abstract class VirtualViewContainerState {
50+ protected val prerenderRatio: Double = ReactNativeFeatureFlags .virtualViewPrerenderRatio()
51+ protected val hysteresisRatio: Double = ReactNativeFeatureFlags .virtualViewHysteresisRatio()
52+ protected abstract val virtualViews: MutableCollection <VirtualView >
53+ protected val emptyRect: Rect = Rect ()
54+ protected val visibleRect: Rect = Rect ()
55+ protected val prerenderRect: Rect = Rect ()
56+ protected val hysteresisRect: Rect = Rect ()
57+ protected val onWindowFocusChangeListener =
6158 if (ReactNativeFeatureFlags .enableVirtualViewWindowFocusDetection()) {
6259 ViewTreeObserver .OnWindowFocusChangeListener {
6360 debugLog(" onWindowFocusChanged" )
@@ -66,8 +63,18 @@ internal class VirtualViewContainerState {
6663 } else {
6764 null
6865 }
66+ protected val scrollView: ViewGroup
6967
70- private val scrollView: ViewGroup
68+ companion object {
69+ @JvmStatic
70+ fun create (scrollView : ViewGroup ): VirtualViewContainerState {
71+ return if (ReactNativeFeatureFlags .enableVirtualViewContainerStateExperimental()) {
72+ VirtualViewContainerStateExperimental (scrollView)
73+ } else {
74+ VirtualViewContainerStateClassic (scrollView)
75+ }
76+ }
77+ }
7178
7279 constructor (scrollView: ViewGroup ) {
7380 this .scrollView = scrollView
@@ -76,13 +83,13 @@ internal class VirtualViewContainerState {
7683 }
7784 }
7885
79- public fun cleanup () {
86+ fun cleanup () {
8087 if (onWindowFocusChangeListener != null ) {
8188 scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
8289 }
8390 }
8491
85- public fun onChange (virtualView : VirtualView ) {
92+ open fun onChange (virtualView : VirtualView ) {
8693 if (virtualViews.add(virtualView)) {
8794 debugLog(" add" , { " virtualViewID=${virtualView.virtualViewID} " })
8895 } else {
@@ -91,91 +98,27 @@ internal class VirtualViewContainerState {
9198 updateModes(virtualView)
9299 }
93100
94- public fun remove (virtualView : VirtualView ) {
101+ open fun remove (virtualView : VirtualView ) {
95102 assert (virtualViews.remove(virtualView)) {
96103 " Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID} "
97104 }
98105 debugLog(" remove" , { " virtualViewID=${virtualView.virtualViewID} " })
99106 }
100107
101108 // Called on ScrollView onLayout or onScroll
102- public fun updateState () {
109+ fun updateState () {
103110 debugLog(" updateState" )
104111 updateModes()
105112 }
106113
107- private fun updateModes (virtualView : VirtualView ? = null) {
108- scrollView.getDrawingRect(visibleRect)
109-
110- // This happens because ScrollView content isn't ready yet. The danger here is if ScrollView
111- // intentionally goes but curently ScrollView and v1 Fling use this check to determine if
112- // "content ready"
113- if (visibleRect.isEmpty()) {
114- debugLog(" updateModes" , { " scrollView visibleRect is empty" })
115- return
116- }
117-
118- prerenderRect.set(visibleRect)
119- prerenderRect.inset(
120- (- prerenderRect.width() * prerenderRatio).toInt(),
121- (- prerenderRect.height() * prerenderRatio).toInt(),
122- )
123-
124- if (hysteresisRatio > 0.0 ) {
125- hysteresisRect.set(prerenderRect)
126- hysteresisRect.inset(
127- (- visibleRect.width() * hysteresisRatio).toInt(),
128- (- visibleRect.height() * hysteresisRatio).toInt(),
129- )
130- }
131-
132- val virtualViewsIt =
133- if (virtualView != null ) listOf (virtualView) else virtualViews.toMutableSet()
134- virtualViewsIt.forEach { vv ->
135- val rect = vv.containerRelativeRect
136-
137- var mode: VirtualViewMode ? = VirtualViewMode .Hidden
138- var thresholdRect = emptyRect
139- when {
140- rectsOverlap(rect, visibleRect) -> {
141- thresholdRect = visibleRect
142- if (onWindowFocusChangeListener != null ) {
143- if (scrollView.hasWindowFocus()) {
144- mode = VirtualViewMode .Visible
145- } else {
146- mode = VirtualViewMode .Prerender
147- }
148- } else {
149- mode = VirtualViewMode .Visible
150- }
151- }
152- rectsOverlap(rect, prerenderRect) -> {
153- mode = VirtualViewMode .Prerender
154- thresholdRect = prerenderRect
155- }
156- (hysteresisRatio > 0.0 && rectsOverlap(rect, hysteresisRect)) -> {
157- mode = null
158- }
159- }
160-
161- if (mode != null ) {
162- vv.onModeChange(mode, thresholdRect)
163- debugLog(
164- " updateModes" ,
165- {
166- " virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect "
167- },
168- )
169- }
170- }
171- }
114+ protected abstract fun updateModes (virtualView : VirtualView ? = null)
172115}
173116
174117private const val DEBUG_TAG : String = " VirtualViewContainerState"
175- private val IS_DEBUG_BUILD =
118+ internal val IS_DEBUG_BUILD =
176119 ReactBuildConfig .DEBUG || ReactBuildConfig .IS_INTERNAL_BUILD || ReactBuildConfig .ENABLE_PERFETTO
177120
178- internal inline fun debugLog (subtag : String , block : () -> String = { "" }) {
121+ private inline fun debugLog (subtag : String , block : () -> String = { "" }) {
179122 if (IS_DEBUG_BUILD && ReactNativeFeatureFlags .enableVirtualViewDebugFeatures()) {
180123 FLog .d(" $DEBUG_TAG :$subtag " , block())
181124 }
0 commit comments