Is it possible to make a layout modifier that woul...
# compose
v
Is it possible to make a layout modifier that would extend clickable area beyond the layout's measured bounds? I know compose does this internally with the minimum touch target size:
distanceInMinimumTouchTarget
in LayoutNodeWrapper. I'm looking for a solution that would allow providing a custom minimum size per-element without interfering with the visual layout around it (so no padding)
a
You can provide your own `ViewConfiguration`:
Copy code
val viewConfiguration = object : ViewConfiguration by LocalViewConfiguration.current {
    override val minimumTouchTargetSize: DpSize
        get() = DpSize(100.dp, 100.dp)
}
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
    Layout()
}
v
thanks!
So probably no way to do it with a layout modifier for existing components? This will require wrapping stuff which is a bit more refactoring work
j
You can create a custom Modifier for this and re-calculate min size with LayoutModifier, see https://androidx.tech/artifacts/compose.material3/material3/1.0.1-source/androidx/compose/material3/TouchTarget.kt.html for example :)
v
Thanks Joel! I took a look at that before as well, but it seems like that would just essentially force a padding around the element, creating empty space around it. (Or I have misunderstood... 😅)
j
Yeah right want click overlap across composable nodes? Then that padding wouldnt work. Probably something else I havent learned yet. Dont remember if we had that earlier in regular Android Views?
v
Not sure if it's possible with Views, I don't have much experience with them 🤔 I'm just wondering if the modifier system is flexible enough in compose to allow doing something like this. I'm not looking for touch overlap, but allow pressing some small buttons easily that are in middle of other non-clickable content. Without adding a lot of empty space in the layout to accommodate the click area
j
Right, Yeah Overlap I mean into parent composable bounds regardless if content under or not. It feels like it should be possible. At least possible detect click outside and delegate to click listener.
a
Yes it's possible and I've already gave a solution. If you really want to use a modifier, since a layout modifier will affect sizes of both touch detection and layout, you can't do that by adding a modifier. Instead you must replace every
Modifier.clickable
with your own implementation, which is likely to be much more complicated.
v
Alright, thanks a lot for the detailed explanation Albert! I was theoretically interested in if it would be possible with a modifier as it helps me understand the deeper foundations of compose to explore all alternatives.
And thanks Joel for finding that hack, I will take a look 👀
a
Btw the modifier in the link above will likely reduce rendering efficiency and graphics quality, and it will require 2 frames for the UI to settle down. If you want to go that way, use layout modifier instead of graphics layout modifier, and don't use
onSizeChanged
.
j
@Albert Chang Thanks for clarify, thats what meant kind a hacky. Would probably do your variant as well decorating composables, can always hide it in composable re-usable.
v
Here's the source code for this demo:
Copy code
// clipping bug(?)
@Composable
fun Demo4() {
    val viewConfiguration: ViewConfiguration = LocalViewConfiguration.current
    val minTouchViewConfiguration = object : ViewConfiguration by viewConfiguration {
        override val minimumTouchTargetSize = DpSize(200.dp, 200.dp)
    }
    CompositionLocalProvider(LocalViewConfiguration provides minTouchViewConfiguration) {
        Box(
            Modifier
                .size(500.dp)
                .background(Color.Red),
            contentAlignment = Alignment.Center
        ) {
            Row(
                Modifier
                    .background(Color.White)
                    .padding(10.dp)
                    .clip(RoundedCornerShape(30.dp)),
                horizontalArrangement = Arrangement.spacedBy(10.dp)
                ) {
                repeat(3) {
                    Box(
                        Modifier
                            .background(Color.Blue)
                            .size(90.dp)
                            .clickable { })
                }
            }
        }
    }
}
Oh, and this only happens when the
.clip
modifier is applied to the intermediate parent (white background). It works without it.
j
Your click area is 200x200, square. If increase width of min touch area closer to box size 500x500, does it work?
v
Hmm. If I increase it a lot (400dp, 500dp) it works horizontally a bit, but the cutoff is still very asymmetric on the major/minor axes. I tested also removing the clip modifier again and it makes it symmetric again.
So it seems like when using
clip = true
in
.graphicsLayer
the minimum touch size is calculated with that layer without giving the children a chance to test their own minimum areas 🤔
Any ideas on how to work around this? And is this intended behaviour?
In other words, `
Copy code
.graphicsLayer { clip = true },
in a parent breaks the minimum touch target sizes of the children
I'm not very familiar with the compose touch/hit detection, but it seems like if the parent is on a separate layer(?) it must first be inside the layer's area before doing hit detection for children