Skip to content

Commit 9d18367

Browse files
zoontekmeta-codesync[bot]
authored andcommitted
Fix measureInWindow on Android edge-to-edge (#56056)
Summary: Fixes `measureInWindow` on Android when edge-to-edge is enabled. Both `ReactSurfaceView.viewportOffset` and `RootViewUtil.getViewportOffset` subtract the visible window frame (status bar insets, split screen offsets) from `getLocationOnScreen` / `getLocationInWindow` coordinates. This is incorrect in edge-to-edge mode, where the content already extends behind system bars. Related issue: #50509 ## Changelog: [ANDROID] [FIXED] - Fix `measureInWindow` returning incorrect coordinates when edge-to-edge is enabled Pull Request resolved: #56056 Test Plan: Tested `measureInWindow` with and without edge-to-edge enabled, in both single-window and split-screen configurations. Verified that returned coordinates correctly reflect the view's position relative to the visible content area. For that, in `RNTesterActivity.kt`, comment: ```kt // reactDelegate?.reactRootView?.let { rootView -> // val insetsType: Int = // WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() // val windowInsetsListener = { view: View, windowInsets: WindowInsetsCompat -> // val insets = windowInsets.getInsets(insetsType) // (view.layoutParams as FrameLayout.LayoutParams).apply { // setMargins(insets.left, insets.top, insets.right, insets.bottom) // } // WindowInsetsCompat.CONSUMED // } // ViewCompat.setOnApplyWindowInsetsListener(rootView, windowInsetsListener) // } ``` And in `RNTesterAppShared.js` replace `export default RNTesterApp` with: ```js export default () => { const ref = React.useRef<View>(null); const [measure, setMeasure] = React.useState<string>(''); const [measureInWindow, setMeasureInWindow] = React.useState<string>(''); const [onLayoutOutput, setOnLayoutOutput] = React.useState<string>(''); React.useLayoutEffect(() => { ref.current?.measure( ( x: number, y: number, width: number, height: number, pageX: number, pageY: number, ) => { setMeasure( `(${x}, ${y}) / ${width} x ${height}; \n page=(${pageX}, ${pageY})`, ); }, ); }, []); React.useLayoutEffect(() => { ref.current?.measureInWindow( (x: number, y: number, width: number, height: number) => { setMeasureInWindow(`(${x}, ${y}) / ${width} x ${height};})`); }, ); }, []); return ( <View ref={ref} style={{flex: 1, justifyContent: 'center'}} onLayout={e => { setOnLayoutOutput(JSON.stringify(e.nativeEvent.layout)); }}> <Text>Measure: {measure}</Text> <Text>measureInWindow: {measureInWindow}</Text> <Text>onLayout: {onLayoutOutput}</Text> </View> ); }; ``` ## Screenshots: #### Android 16 / edge-to-edge on - current behavior <img width="265" height="489" alt="Android 16 (edge-to-edge on - no fix)" src="https://github.com/user-attachments/assets/71c62742-e835-42f1-9e79-23d5fc37e9a2" /> #### Android 16 / edge-to-edge on - with fix <img width="265" height="489" alt="Android 16 (edge-to-edge on - fixed)" src="https://github.com/user-attachments/assets/9e5b5095-3b3a-490d-bf81-2ef721a9db5e" /> #### Android 14 / edge-to-edge off - current behavior <img width="260" height="483" alt="Android 14 (edge-to-edge off - no fix)" src="https://github.com/user-attachments/assets/31c1776b-8abc-45ac-8796-de81a62aff78" /> #### Android 14 / edge-to-edge off - with fix (no change) <img width="260" height="483" alt="Android 14 (edge-to-edge off - fixed)" src="https://github.com/user-attachments/assets/b60e4c2f-5893-4383-8ae1-547cf07aa8c9" /> Reviewed By: javache Differential Revision: D101308387 Pulled By: alanleedev fbshipit-source-id: 27dfe0692b2da6c63bada685bb9c2544673f097b
1 parent 4aa375b commit 9d18367

2 files changed

Lines changed: 38 additions & 17 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import android.graphics.Rect
1515
import android.view.KeyEvent
1616
import android.view.MotionEvent
1717
import android.view.View
18+
import androidx.core.view.ViewCompat
19+
import androidx.core.view.WindowInsetsCompat
1820
import com.facebook.common.logging.FLog
1921
import com.facebook.react.ReactRootView
2022
import com.facebook.react.bridge.ReactContext
@@ -26,6 +28,7 @@ import com.facebook.react.uimanager.IllegalViewOperationException
2628
import com.facebook.react.uimanager.JSKeyDispatcher
2729
import com.facebook.react.uimanager.JSPointerDispatcher
2830
import com.facebook.react.uimanager.JSTouchDispatcher
31+
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2932
import com.facebook.systrace.Systrace
3033
import java.util.Objects
3134
import kotlin.math.max
@@ -46,16 +49,24 @@ public class ReactSurfaceView(context: Context?, internal val surface: ReactSurf
4649

4750
private val viewportOffset: Point
4851
get() {
49-
val locationOnScreen = IntArray(2)
50-
getLocationOnScreen(locationOnScreen)
51-
52-
// we need to subtract visibleWindowCoords - to subtract possible window insets, split
53-
// screen or multi window
54-
val visibleWindowFrame = Rect()
55-
getWindowVisibleDisplayFrame(visibleWindowFrame)
56-
locationOnScreen[0] -= visibleWindowFrame.left
57-
locationOnScreen[1] -= visibleWindowFrame.top
58-
return Point(locationOnScreen[0], locationOnScreen[1])
52+
val locationInWindow = IntArray(2)
53+
getLocationInWindow(locationInWindow)
54+
55+
if (!isEdgeToEdgeFeatureFlagOn) {
56+
// When not in edge-to-edge mode, subtract the top system bar insets so the offset is
57+
// relative to the content area (below the status bar / cutout).
58+
ViewCompat.getRootWindowInsets(this)?.apply {
59+
val insets =
60+
getInsets(
61+
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout()
62+
)
63+
64+
locationInWindow[0] -= insets.left
65+
locationInWindow[1] -= insets.top
66+
}
67+
}
68+
69+
return Point(locationInWindow[0], locationInWindow[1])
5970
}
6071

6172
init {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootViewUtil.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
package com.facebook.react.uimanager
99

1010
import android.graphics.Point
11-
import android.graphics.Rect
1211
import android.view.View
1312
import androidx.annotation.UiThread
13+
import androidx.core.view.ViewCompat
14+
import androidx.core.view.WindowInsetsCompat
1415
import com.facebook.infer.annotation.Assertions
16+
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
1517

1618
public object RootViewUtil {
1719
/** Returns the root view of a given view in a react application. */
@@ -34,12 +36,20 @@ public object RootViewUtil {
3436
val locationInWindow = IntArray(2)
3537
v.getLocationInWindow(locationInWindow)
3638

37-
// we need to subtract visibleWindowCoords - to subtract possible window insets, split
38-
// screen or multi window
39-
val visibleWindowFrame = Rect()
40-
v.getWindowVisibleDisplayFrame(visibleWindowFrame)
41-
locationInWindow[0] -= visibleWindowFrame.left
42-
locationInWindow[1] -= visibleWindowFrame.top
39+
if (!isEdgeToEdgeFeatureFlagOn) {
40+
// When not in edge-to-edge mode, subtract the top system bar insets so the offset is
41+
// relative to the content area (below the status bar / cutout).
42+
ViewCompat.getRootWindowInsets(v)?.apply {
43+
val insets =
44+
getInsets(
45+
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout()
46+
)
47+
48+
locationInWindow[0] -= insets.left
49+
locationInWindow[1] -= insets.top
50+
}
51+
}
52+
4353
return Point(locationInWindow[0], locationInWindow[1])
4454
}
4555
}

0 commit comments

Comments
 (0)