Marko Novakovic
05/11/2021, 7:00 PM+
. Problem I have with my current implementation is that top layer with cloud is consuming all inputs so I can’t interact with rest of the app. Do you have any ideas how to do this. Code in 🧵Marko Novakovic
05/11/2021, 7:01 PMimport androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import kotlin.math.roundToInt
@Composable
fun CircleMenu() {
var offset by remember { mutableStateOf(Offset(0f, 0f)) }
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offset += dragAmount
}
},
) {
Cloud(offset = offset)
}
}
@Composable
private fun Cloud(offset: Offset) {
Box(
modifier = Modifier.offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) },
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.cloud),
contentDescription = null,
)
Icon(
imageVector = Icons.Filled.Add,
tint = Color.White,
contentDescription = null,
)
}
}
Marko Novakovic
05/11/2021, 7:02 PM@Composable
fun Home() {
Box {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { HomeAppBar() },
) {
...
}
CircleMenu()
}
}
Chris Sinco [G]
05/11/2021, 7:10 PMModifier.fillMaxSize()
in your CircleMenu
might be the issue? I think if you change that to Modifier.wrapContentSize()
the gestures should only be captured on itCasey Brooks
05/11/2021, 7:13 PMCircleMenu
inside your Scaffold
such that the other pieces of UI are somewhere in its parents’ layoutsMarko Novakovic
05/11/2021, 7:16 PMMarko Novakovic
05/11/2021, 7:17 PMCasey Brooks
05/11/2021, 7:19 PMBox
, each handling a different portion of the UI. Each of these layers needs to be nested within the others in order to have the input event propagate correctly. Give me a min to gather the relevant bits from my app into a something like a minimal exampleMarko Novakovic
05/11/2021, 7:21 PMModifier.wrapContentSize()
is not wanted behaviour. It detects drag gesture just inside a box at a original cloud positionMarko Novakovic
05/11/2021, 7:25 PMBox
inside Scaffold
with other content has the original issueMarko Novakovic
05/11/2021, 7:26 PMChris Sinco [G]
05/11/2021, 7:53 PM@Preview
@Composable
fun PlaygroundPreview2() {
var cloudXOffset by remember { mutableStateOf(0f) }
var cloudYOffset by remember { mutableStateOf(0f) }
Box(Modifier.fillMaxSize()) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { TopAppBar(title = { Text("title") }) }
) {
Column(Modifier.fillMaxSize()) {
Spacer(Modifier.fillMaxHeight(0.25f))
CustomButton()
}
}
Cloud(
modifier = Modifier
.offset { IntOffset(cloudXOffset.roundToInt(), cloudYOffset.roundToInt()) }
.wrapContentSize()
.background(Color.Magenta)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
cloudXOffset += dragAmount.x
cloudYOffset += dragAmount.y
}
}
)
}
}
@Composable
private fun Cloud(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
) {
Icon(
imageVector = Icons.Default.Face,
contentDescription = null,
tint = contentColorFor(Color.Magenta)
)
}
}
Chris Sinco [G]
05/11/2021, 7:57 PMCasey Brooks
05/11/2021, 8:11 PM@Composable
override fun render() {
Box(
modifier = Modifier.fillMaxSize()
) {
MainContent()
// Cloud Menu layer
CloudContainer()
}
}
@Composable
fun MainContent() {
var mainContentCounter by remember { mutableStateOf(0) }
Button(onClick = { mainContentCounter++ }) {
Text("main counter: $mainContentCounter")
}
}
@Composable
fun CloudContainer() {
var offset by remember { mutableStateOf(Offset(0f, 0f)) }
Box(
modifier = Modifier.fillMaxSize()
) {
Cloud(offset = offset) { offset = it }
}
}
@Composable
fun Cloud(offset: Offset, updateOffset: (Offset)->Unit) {
var cloudCounter by remember { mutableStateOf(0) }
var accumulatedOffset by remember { mutableStateOf(Offset.Zero) }
Box(
modifier = Modifier
.offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, dragAmount ->
change.consumeAllChanges()
accumulatedOffset += dragAmount
updateOffset(offset + accumulatedOffset)
}
)
}
.background(MaterialTheme.colors.primary)
.clickable { cloudCounter++ }
.size(
width = 50.dp,
height = 50.dp
),
contentAlignment = Alignment.Center
) {
Text("Cloud counter: $cloudCounter")
}
}
Looking at @Marko Novakovic’s code again, I think it might be an issue that the pointerInput
modifier is on the cloud’s Box layout, rather than on the Cloud itselfMarko Novakovic
05/11/2021, 8:26 PMChris Sinco [G]
05/11/2021, 9:06 PMpointerInput
and offset
modifiers to be chained together because are moving the dragTarget and the element together. So as you drag, you update the offset, which keeps the dragTarget visually under your finger.Marko Novakovic
05/12/2021, 5:45 AMpointerInput
was on Box
instead of cloud itself. offset
and pointerInput
should indeed be chained together