1

I'm new to Jetpack Compose and trying to figure out how to solve next task:

I need to create a simple transparent AndroidView. And it needs to be used as an overlay for Composable functions.

The problem is that an overlay should be the same size as a compose view under it.

I had some-kind of successful attempt with this:

@Composable
fun BugseeOverlayView() {
    AndroidView(
        modifier = Modifier.fillMaxSize(),
        factory = { ctx ->
            View(ctx).apply {
                layoutParams = LinearLayout.LayoutParams(200, 200) //Hardcoded size
                alpha = 0.0F
            }
        }, update = {
            Bugsee.addSecureView(it) // 3rd party I need to use
        }
    )
}

And then I used it like:

Box {    
    Box(Modifier.fillMaxSize()) {
        BugseeOverlayView()
    }
    Text("Hide me") // or some 'CustomComposableView(param)'
}

This works, but the size is hardcoded.

PS. I need an AndroidView because of third-party tool which accepts android.view.View as a parameter.

Sheikh
  • 1,116
  • 6
  • 15
  • You don't need an AndroidView for your overlay. If you can show a screenshot of what you are trying to achieve I might be able to assist you further. You can use a `Box` composable as your overlay and position it anywhere on the screen you want – Rafsanjani Aug 26 '22 at 08:51
  • Hi @Rafsanjani, I chose AndroidView because it gives access to the instance of android.view.View within it. And this instance of android.view.View is necessary to be passed into third-party tool. – Sheikh Aug 26 '22 at 11:31

1 Answers1

1

You can get size of a Composable in various ways.

1- Modifier.onSizeChanged{intSize->} will return Composable size in pixels you can convert this to dp using LocalDensity.current.run{}. With this approach the size you set will change and there needs to be another recomposition. You can also get size of a Composable from Modifier.onGloballyPositioned either.

val density = LocalDensity.current
var dpSize: DpSize by remember{ mutableStateOf(DpSize.Zero) }
Modifier.onSizeChanged { size: IntSize ->
    density.run { dpSize = DpSize(size.width.toDp(), size.height.toDp()) }
}
Modifier.onGloballyPositioned {layoutCoordinates: LayoutCoordinates ->
    val size = layoutCoordinates.size
    density.run { dpSize = DpSize(size.width.toDp(), size.height.toDp()) }
}

2- If the Composable has fixed size or covers screen you can use

BoxWithConstraints {
   SomeComposable()
   AndroidView(modifier=Modifier.size(maxWidth, maxHeight)
}

3- If you don't have chance to get Composable size and don't want to have another recomposition you can use SubcomposeLayout. Detailed answer is available here how to create a SubcomposeLayout to get exact size of a Composable without recomposition.

When you are able to get size of Composable you can set same size to AndroidView and set layout params to match parent. If that's not what you wish you can still set Modifier.fillMaxSize while using methods above to set layout params

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thanks for help, @Thracian! I am trying Modifier.onGloballyPositioned and I see it gives the right size. But this callback is called at the last stage and AndroidView is already created with wrong layout params. I made a gist to show my test: https://gist.github.com/dsheikherev/d11d43be7ddcbf8dd85dd7042911c1b5 – Sheikh Aug 26 '22 at 11:29
  • That's because you get actual size on recomposition with Modifier.size or Modifier.onGlobabllyPositioned. You will need to modify LinerLayout dimensions in update. This is something i haven't tried just food for thought. You should try SubcomposeLayout. It defers the composition and measure of content until its constraints from its parent are known. So you can get dimensions of Composable and pass it to AndroidView before AndroidView is laid. – Thracian Aug 26 '22 at 11:35
  • SubcomposeLayout works great! That's what i need. Thanks for help, @Thracian! I will mark your answer as correct. BTW, do you know something about the performance of SubcomposeLayout? How is it? – Sheikh Aug 28 '22 at 07:08
  • It's more peformant than having another recomposition but less than using Layout because you call subcompose function. But there are many Composables that use SubcomposeLayout including BoxWithConstraints, TabRow/ScrollableTabRow, Scaffold, LazyColum/Row/Grid which basically we use SubcompeLayout even if don't know we do :) – Thracian Aug 28 '22 at 07:11
  • I use it a lot. With a Slider with emoji here. https://github.com/SmartToolFactory/Compose-Colorful-Sliders. Dynamic chat rows inside a LazyColumn. https://github.com/SmartToolFactory/Flexible-Chat-Box. For instance here for image with handles. https://stackoverflow.com/questions/72802650/is-there-a-way-to-increase-a-composable-size-by-chaining-with-another-modifier. I really use it a lot when i need to measure something like in the link. With DimensionSubComposeLayout is what i use instead of BoxWithConstraints which gives exact size – Thracian Aug 28 '22 at 07:16
  • Also coil library uses it too. For SubcomposeAsyncImage. https://coil-kt.github.io/coil/compose/ – Thracian Aug 28 '22 at 07:17
  • you might achieve the result without SubcomposeLayout either. https://stackoverflow.com/questions/73354911/how-to-get-exact-size-without-recomposition/73357119#73357119 Check `Selectively measuring to match one Sibling to Another` section. Since you want to set second Composable, overlay, to size of first you can measure first one then measure overlay with fixed constraints. SubcomposeLayout is needed you need to set dimensions of Composables when you need to remeasure which is not possible with Layout. I mean like chat rows where you need to find which one is widest first before setting all – Thracian Aug 28 '22 at 09:25