How to get the screen size when in edge-to-edge mode? I just figured out that LocalContext.current.r...
t
How to get the screen size when in edge-to-edge mode? I just figured out that LocalContext.current.resources.displayMetrics only returns widht and height excluding insets And also: LocalConfiguration.current.screenWidthDp do not include the insets.
t
Not sure about this api. Do i need the activity for this. So it is not possible to get max window size and height inside of a composable UI?
a
You can get the activity from the context, but it’s recommended to calculate this once and pass it down.
t
I would like to develop a kind of smart insetPadding which only applies when the component overlapps the area of the inset. And for this i need to know where the exact positions of the insets are.
f
If you use a composable
BoxWithConstraints
, you can access maxHeight and maxWidth, which are the values you'are looking for. Basically it's a
Box
composable but it gives you data in it. https://foso.github.io/Jetpack-Compose-Playground/foundation/layout/boxwithconstraints/
t
The problem is not to get the size of the composable. I will do this in a Layout or a LayoutModifier. But i need the position of the insets. But the current API only returns the size of the insets
s
What are you planning to do with that information?
t
I do have an app with edge-to-edge design and it is very complex to get every edge cases covered. So it depends on the position of the composables if they need to have window insets padding or not. I think if i would have an insetPadding modifier that only applys padding when the composable overlapps with the window inset the code complexity would be much less.
@Albert Chang This code returns the correct window size:
Copy code
val windowRect = remember {
    val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(ctx)
    metrics.bounds
}
Thank you very much.
But maybe one important thing here. This must be recalculated on configuration changes like orientation or others which changes the size.
1
Unfortunately it is not working for all cases. When the Pixel 7 is in landscape mode it show a width of 2400 but the real usable size is only 2264. And it looks like the insets are based on 2264 width and not 2400. In Portrait mode the 2400 height is correct.
s
I do have an app with edge-to-edge design and it is very complex to get every edge cases covered. So it depends on the position of the composables if they need to have window insets padding or not. I think if i would have an insetPadding modifier that only applys padding when the composable overlapps with the window inset the code complexity would be much less.
Oh boy, that sounds like a great way to make the complexity explode 😅 The entire inset modifier system is setup in a way that if the insets were already consumed by a parent, the child will not receive those insets again. If you instead just consume the insets as the API is expecting you to, you really shouldn't have to do anything special about this, it will just work, it certainly does for our app where we use insets on all of our screens this way. Do you have an example of something not working for you to see if you're perhaps doing something else wrong?
a
+1 to
WindowMetricsCalculator.computeCurrentWindowMetrics
for calculating the current window size There is a version in later releases of
androidx.window
that takes a
Context
instead of an
Activity
, https://github.com/google/accompanist/pull/1576 can be a good reference. https://issuetracker.google.com/issues/305993708 is an upstream issue for adding this as a built-in method.
displayMetrics
is almost never what you want - the size of the display does not relate to the size of your
Window
And yep
Configuration
doesn’t give you the size of the window either, both due to insets and due to rounding
t
No WindowMetricsCalculator does not work for me. I use this code now:
Copy code
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val screenWidthPx = with(density) { screenWidth.toPx().toInt() }
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
val screenHeightPx = with(density) { screenHeight.toPx().toInt() }
And than just add the Padding of the insets to the size. In my tests this work. But maybe i should use WindowInsets.safeDrawing for screen size calculation maybe. And than for padding i can use a different one. So all feels a little random here. Maybe the API should be a little bit more consistent here.
I know how the consuming of insets is implemented. Unfortunately it is only possible in modifiers to change it and also to read it. That e.g. does not work when you do want to program some custom layout manager with multiple elements. And for LazyLists you can not use modifier at all for padding. I mean i was able to get everything working but the code is complex and also difficult to extend or change later.
Working code for me looks like this for now (Not a modifier yet);
Copy code
@Composable
fun SmartInsetsProvider(
    insets: WindowInsets,
    content: @Composable @UiComposable (insetsPadding: PaddingValues) -> Unit
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current

    var insetPaddingValues by remember { mutableStateOf(PaddingValues()) }

    val screenWidth = LocalConfiguration.current.screenWidthDp.dp
    val screenWidthPx = with(density) { screenWidth.toPx().toInt() }
    val screenHeight = LocalConfiguration.current.screenHeightDp.dp
    val screenHeightPx = with(density) { screenHeight.toPx().toInt() }

    Layout(content = { content(insetPaddingValues) }) { measurable, constraints ->
        if (measurable.size > 1) throw IllegalArgumentException("Only one child composable allowed!")
        val placeable = measurable.first().measure(constraints)
        val left = insets.getLeft(density, layoutDirection)
        val top = insets.getTop(density)
        val bottom = insets.getBottom(density)
        val right = insets.getRight(density, layoutDirection)

        layout(placeable.width, placeable.height) {
            coordinates?.positionInWindow()?.let { posInWindow ->
                val topPaddingPx = max(0, top - posInWindow.y.toInt())
                val bottomDistance = screenHeightPx + top + bottom - posInWindow.y.toInt() - placeable.height
                val bottomPaddingPx = max(0, bottom - bottomDistance)
                val leftPaddingPx = max(0, left - posInWindow.x.toInt())
                val rightDistance = screenWidthPx + left + right - posInWindow.x.toInt() - placeable.width
                val rightPaddingPx = max(0, right - rightDistance)
                with(density) {
                    insetPaddingValues = PaddingValues.Absolute(
                        top = topPaddingPx.toDp(),
                        bottom = bottomPaddingPx.toDp(),
                        left = leftPaddingPx.toDp(),
                        right = rightPaddingPx.toDp()
                    )
                }
            }

            placeable.place(IntOffset.Zero)
        }
    }
}
d
Are you sure this works in split-screen?
t
As far as i tested it works also in split-screen but maybe i missed some edge cases.
Here you see the values of the insets with and without spilt screen. And when it is in splitscreen mode the navigationbar insets are not present because they need to be handled by the other app. :
d
That's exactly what people are telling you above, using
screenWidthDp
and
screenHeightDp
gives you the screen size, not your window
t
No it is the window. Sorry i missed this
2199 -> 1050
I also need to check if it works for animations. I will do that now
Because sometimes this values are one frame behind.
d
Right I see, okay, hope I've not confused you. Guess it's just wrongly named by Google
screen
if it's actually window sizes
t
Yes exactly. And also it should contain drawable window size including insets
Maybe they could add this. Maybe i will file a feature request 😄
In this recording the animation looks a little bit bumpy but on the device it is fine. But yea i think the code do not handle animations perfectly. So maybe the padding is one frame behind.
Copy code
var animateStart by remember { mutableStateOf(false) }
val detailVisible by animateFloatAsState(
    targetValue = if (animateStart) 1f else 0f,
    label = "animation started",
    animationSpec = tween(2000)
)
val animSize = 150.dp
SmartInsetsProvider(
    modifier = Modifier.offset(y = animSize * detailVisible),
    insets = WindowInsets.safeDrawing
) { insetPadding ->
    Row(
        Modifier
            .background(Color.Red)
            .padding(insetPadding)
            .background(Color.LightGray)
            .padding(8.dp)
    ) {
        Text(
            text = "Test"
        )
        Spacer(Modifier.width(8.dp))
        Button(onClick = { animateStart = animateStart.not() }) {
            Text("Animate")
        }
    }
}
a
Totally agreed that the
Configuration.screen*
values are confusing:
Configuration.screenWidthDp
,
Configuration.screenHeightDp
Configuration.orientation
and others all refer to the window size… almost, they are different by an amount of some of the insets and by rounding. But they aren’t referring to the display size. What was the issue with
WindowMetricsCalculator.computeCurrentWindowMetrics
? That should actually give you the window size (and not an amount different by some amount of insets)
t
It is smaller than the actual window size because it excludes the insets.
a
That is what you’re seeing for
computeCurrentWindowMetrics
? That should include the insets - if not, that’s a bug
t
Yes sorry you are right. It returns the real screensize but this is not useable in some cases e.g.: Landscape mode for Pixel 7. There the camera area on the left is black. So the
computeCurrentWindowMetrics
returns a width of 2400 but usable is only 2264 even in edge-to-edge mode.
a
We’re changing that in `androidx.activity`’s
enableEdgeToEdge
starting with `1.9.0-alpha02`: https://developer.android.com/jetpack/androidx/releases/activity#1.9.0-alpha02 The underlying API is `layoutInDisplayCutoutMode`: setting it to https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS will allow the app to draw behind cutouts like the Pixel 7 in landscape mode
😎 1
Also
computeCurrentWindowMetrics
computes the window size - if the app is fullscreen, that’ll be the same as the screen size, but not in general
t
We’re changing that in `androidx.activity`’s
enableEdgeToEdge
starting with `1.9.0-alpha02`: https://developer.android.com/jetpack/androidx/releases/activity#1.9.0-alpha02
Nice looks good. Seems to work correctly for my solution.
Just to let you know I decided to go with this code now to get the window size. It looks reliable to me:
Copy code
@Composable
fun getWindowSize(): IntSize {
    val view = LocalView.current
    return remember {
        val windowWidth = view.rootView.width
        val windowHeight = view.rootView.height
        IntSize(windowWidth, windowHeight)
    }
}
a
Two issues with that method: • That will only be the window size if the
LocalView
fills up the entire window size • That won’t return updated sizes when the window size changes
t
Ok thx for you feedback. So yes indeed but for my usecase i do need only the window size of the compose area which is drawable by myself. But i do need to get changes. I will try to find a workaround to update this size when it changes.
1141 Views