In the traditional Android view system most key event dispatchers in views and activities had a Boolean return type that would help the parent view figure out if the input was consumed by a child down the line or not and could handle both cases accordingly.
My question is how do you do the same thing in Jetpack Compose?
Consider the following sample code snippet:
#Composable
private fun Outer() {
Box(
Modifier
.padding(30.dp)
.height(400.dp)
.width(300.dp)
.background(Color.Green)
.pointerInput(true) {
detectDragGestures { change, dragAmount ->
// Do something only if the inner compose didn't handle it
}
},
contentAlignment = Alignment.Center
) {
Inner()
}
}
#Composable
private fun Inner() {
Box(
Modifier
.fillMaxWidth(0.5f)
.fillMaxHeight(0.5f)
.background(Color.Blue)
.pointerInput(true) {
detectDragGestures { change, dragAmount ->
// Do something if change is less than a specific amount otherwise
// tell parent I didn't consume the input
}
}
)
}
So for instance if a drag gesture happens inside the blue inner Box it will only be consumed if it is less than a specific amount other wise the outer green Box will handle the gesture.
Or is it possible so that both can consume the gestures?
Related
I have BottomDialog Fragment which consists only of Composable (Row(title) and LazyColumn).
The first issue I faced was when you scroll your list down and then you try to scroll up list won't scroll and the dialog starts to minimize. This is solved with
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())
But now user can't minimize a dialog when he tries to do it by touching a title. And there is my question, How to solve this?
Minimum reproducible code
During creating this example I found that I can maximize dialog when touching the title, also I can start moving the action going to the top with my finger (to start to expand it) and then move the finger to the bottom of the screen, in this way the dialog will be dismissed, but still can't minimize it in a non-tricky way.
ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
Theme {
Column(modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
Row {
Text(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
text = "Title"
)
}
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()
) {
items(100) {
Text(
text = "Item $it",
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
)
}
}
}
}
}
}
Please do not propose BottomSheetScaffold. Because I want to create a standardized bottom dialog. And to use it I will need just pass a list of items and a Compose function for one item.
IMO BottomSheetScaffold shouldn't be released at all, cause it was designed "not well". Just imagine earlier before Jetpack Compose you write your code around the bottom dialog, nice layering. No.
As a temp decision. I just think of LazyColumn works properly so I need to wrap my header to the LazyColumn.
So I created this function. And Just pass here any Composable
#Composable
fun TrickyLazyColumn(content: #Composable () -> Unit) {
LazyColumn {
items(
items = listOf(1),
itemContent = {
content.invoke()
})
}
}
I have many Composables and I want to collapse Composable code inside like in xml. Is there extension for that?
Your post title is a bit misleading, but I think your'e asking how to collapse/expand "code" not the actual widget/ui.
I'm not sure if this is exactly what you want, but you can expand/collapse a specific area of your code if you wrap them within region/endregion without the need of any plugin or configuration, its almost the same behavior that your'e expecting from the xml editor, and you can do this anywhere not only to a function.
expanded code region
collapsed code region
Sample Inner composable expanded
Sample Inner composable collapsed
If you wish to make your Column collapse or expand without animation you simply need to add a if statement and set true to display false to collapse
var visible by remember {
mutableStateOf(true)
}
Column(modifier = Modifier.fillMaxSize()) {
Text("Click to expand or collapse", modifier = Modifier
.fillMaxWidth()
.clickable {
visible = !visible
}
)
if(visible) {
// Content to be collapsed or displayed
}
}
If you wish to collapse or expand with animation you can check out AnimatedVisbility composable
var visible by remember {
mutableStateOf(true)
}
Column(modifier = Modifier.fillMaxSize()) {
Text("Click to expand or collapse", modifier = Modifier
.fillMaxWidth()
.clickable {
visible = !visible
}
)
AnimatedVisibility(visible = visible) {
Column {
// Content to be collapsed or displayed
}
}
}
I wanted to make a list of all the elements I would draw in my composable fragment. And make it as easy to maintain as possible.
So I thought, I should not put the list of elements inside my fragment, but outside, in a class that will stock and order those elements.
Here is the solution I came with :
interface SimpleDraw {
#Composable
fun Draw()
}
Create differentes implementation of my interface
class WelcomeBottomSheetElementsImpl : WelcomeBottomSheetElements {
override fun listOfItemInBottomSheet(): List<SimpleDraw> {
return listOf(
insiert here my list of instances of my interface)
fun LazyListScope.BottomsheetBody(
welcomeElements: List<SimpleDraw>,
clickViewModel: ClickViewModel
) {
items(items = welcomeElements) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = MaterialTheme.colors.background)
.padding(start = 10.dp, end = 10.dp, top = 5.dp)
) {
it.Draw()
}
}
}
So far it works great ! Produces the result I was looking at, but it feels off. It feels like this is not the way to do it in Compose.
Is this way correct or am I wrong ?
I understand that architecturally this is definitely not a good thing to do, but I have embedded a for loop in a composable to update state as follows:
#Composable
fun WorkScreen(name: String?) {
var text by remember {
mutableStateOf(0)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text(text = "YOU PRESSED ME $text")
}
for (i in 1..100) {
text = i
}
}
My expectation is that when I switch to this screen the for loop should update the mutableState and hence cause a recomposition which causes the time to tick up. However, instead I just get YOU PRESSED ME 0 if I put the for loop below the Box function, or I get YOU PRESSED ME 100 if I put it above the Box function.
The following question: Why my composable not recomposing on changing value for MutableState of HashMap?, does seem to be quite similar, but I'm not sure how it applies here. It seems to me I am updating the text value to be i!
You shouldn't change view state directly from the composable view builder, because compose functions will be recalled often during recomposition, so your calculation will be repeated. You should use side effects instead.
If you need to show dynamic change of the value to user, then you should use animation, as Gabriele's answer suggests.
An other option is updating the value manually. Inside LaunchedEffect you can use suspend functions, so you can change the value with a needed delay:
LaunchedEffect(Unit) {
for (i in 1..100) {
delay(1000) // update once a second
text = i
}
}
You should use an animation where you define how often you want to update the text applying it with a side effect.
For example:
var targetValue by remember { mutableStateOf(0) }
val value by animateIntAsState(
targetValue = targetValue,
animationSpec = tween( durationMillis = 2000 )
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text(text = "YOU PRESSED ME $value")
}
LaunchedEffect(Unit) {
targetValue = 100
}
I can't seem to find much information on touch handling in Compose.
In the specific case I'm looking at I have a list like this:
#Composable
fun MyListComposable(items: List<Item>) {
LazyColumn(
contentPadding = paddingValues(listHorizontalMargin, listVerticalMargin),
) {
// Init items emitted for brevity
}
}
This list is contained in a parent which uses the swipeable modifier, something like this.
Card(
modifier = Modifier.swipeable(
state = state,
anchors = mapOf(
0.dp.value to DrawerState.OFFSCREEN,
50.dp.value to DrawerState.PEEKING,
maxHeight.value to DrawerState.EXPANDED,
),
reverseDirection = true,
thresholds = { _, _ -> FractionalThreshold(0f) },
orientation = Orientation.Vertical
) {
MyListComposable(items)
}
My problem is the list swallows all touches, so the swipable is never invoked. So my question is, is there a way to stop lazy column swallowing touches?