@@ -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 " 
181124  }
0 commit comments