https://kotlinlang.org logo
Title
v

vide

04/13/2023, 1:11 AM
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

Albert Chang

04/13/2023, 2:36 AM
You can provide your own `ViewConfiguration`:
val viewConfiguration = object : ViewConfiguration by LocalViewConfiguration.current {
    override val minimumTouchTargetSize: DpSize
        get() = DpSize(100.dp, 100.dp)
}
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
    Layout()
}
v

vide

04/13/2023, 6:40 AM
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

Joel Denke

04/13/2023, 7:21 AM
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

vide

04/13/2023, 7:34 AM
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

Joel Denke

04/13/2023, 7:42 AM
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

vide

04/13/2023, 7:44 AM
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

Joel Denke

04/13/2023, 7:48 AM
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

Albert Chang

04/13/2023, 7:53 AM
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

vide

04/13/2023, 7:56 AM
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

Albert Chang

04/13/2023, 8:00 AM
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

Joel Denke

04/13/2023, 9:54 AM
@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

vide

04/18/2023, 3:50 AM
I tried providing a modified
minimumTouchTargetSize
with
LocalViewConfiguration
but I ran into some... unintuitive behaviour with Rows. It doesn't register touches to the ends of the row horizontally, but it works perfectly vertically 🤔 Is this what is supposed to happen or is there some kind of bug in play? See video!
Here's the source code for this demo:
// 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

Joel Denke

04/18/2023, 8:52 AM
Your click area is 200x200, square. If increase width of min touch area closer to box size 500x500, does it work?
v

vide

04/18/2023, 2:10 PM
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, `
.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