2

I have this composable here with a history, an input/output, and a button to show/hide the history (I have removed the parameters unnecessary to my concern):

@Composable
fun MainWindow(
    isShowingHistory: Boolean,
    showHistory: () -> Unit,
    hideHistory: () -> Unit,
) {
    Column(modifier = Modifier.fillMaxSize()) {
        History(modifier = Modifier.weight(1f))
        Surface(
            color = contentColor.copy(0.04f),
            modifier = Modifier.fillMaxWidth(),
        ) {
            Column(
                horizontalAlignment = Alignment.End,
                verticalArrangement = Arrangement.Bottom,
            ) {
                if (!isShowingHistory) {
                    Spacer(modifier = Modifier.weight(1f))
                }
                InputResult(isCompact = isShowingHistory)
                if (!isShowingHistory) {
                    Spacer(modifier = Modifier.weight(1f))
                }
                HistoryIconButton(
                    onClick = if (!isShowingHistory) showHistory else hideHistory,
                    modifier = Modifier.padding(16.dp),
                )
            }
        }
    }
}

It looks like this:

If isShowingHistory is false

isShowingHistory is false

If isShowingHistory is true

isShowingHistory is true

The appearance is correct. However, I want to animate the entrance of History. One way I thought I could make that work is by using AnimatedVisibility on the spacers. However, since the spacers use the modifier weight, I cannot do that since weight only works for direct children of the column.

@Composable
fun MainWindow(
    isShowingHistory: Boolean,
    showHistory: () -> Unit,
    hideHistory: () -> Unit,
) {
    Column(modifier = Modifier.fillMaxSize()) {
        History(modifier = Modifier.weight(1f))
        Surface(
            color = contentColor.copy(0.04f),
            modifier = Modifier.fillMaxWidth(),
        ) {
            Column(
                horizontalAlignment = Alignment.End,
                verticalArrangement = Arrangement.Bottom,
            ) {
                AnimatedVisibility(visible = !isShowingHistory) {
                    Spacer(modifier = Modifier.weight(1f))
                }
                InputResult(isCompact = isShowingHistory)
                AnimatedVisibility(visible = !isShowingHistory) {
                    Spacer(modifier = Modifier.weight(1f))
                }
                HistoryIconButton(
                    onClick = if (!isShowingHistory) showHistory else hideHistory,
                    modifier = Modifier.padding(16.dp),
                )
            }
        }
    }
}

If isShowingHistory is false while using AnimatedVisibility

Using AnimatedVisibility

I have also tried moving the weight modifier to the AnimatedVisibility like this:

@Composable
fun MainWindow(
    isShowingHistory: Boolean,
    showHistory: () -> Unit,
    hideHistory: () -> Unit,
) {
    Column(modifier = Modifier.fillMaxSize()) {
        History(modifier = Modifier.weight(1f))
        Surface(
            color = contentColor.copy(0.04f),
            modifier = Modifier.fillMaxWidth(),
        ) {
            Column(
                horizontalAlignment = Alignment.End,
                verticalArrangement = Arrangement.Bottom,
            ) {
                AnimatedVisibility(
                    visible = !isShowingHistory,
                    modifier = Modifier.weight(1f),
                ) {}
                InputResult(isCompact = isShowingHistory)
                AnimatedVisibility(
                    visible = !isShowingHistory,
                    modifier = Modifier.weight(1f),
                ) {}
                HistoryIconButton(
                    onClick = if (!isShowingHistory) showHistory else hideHistory,
                    modifier = Modifier.padding(16.dp),
                )
            }
        }
    }
}

However, this does not animate the visibility of the spacers, even if I explicitly set the enter and exit parameters to expandVertically and shrinkVertically, since AnimatedVisibility animates its contents, not itself.

How do I animate the weighted spacers? Or better yet, how do I properly animate the visibility of the History composable, following the layout shown in the first two screenshots?

hamthenormal
  • 856
  • 8
  • 21
  • You can animate with Modifier.animateContentSize() but it might not have decent animation based on which Composables they are assigned to, since multiple size animations might not look good, or what Modifiers missing Composables have. I mean the Composables you didn't posted might not change properly. But Column with weight can be animated – Thracian Aug 07 '23 at 18:24

1 Answers1

1

How about this solution

Short demo video

The main goal is to calc height and set it to History box. Let's say you wanted History takes 70% of the parent's height.


[parent].onGloballyPositioned {
            val rect = it.boundsInParent()
            //Lets' say you want the history takes 70% of the height
            historyHeightPx = (rect.bottom * 0.7F).toInt()
        })
AnimatedVisibility(visible = isShowingHistory) {
    History(modifier = Modifier
                    .height(
                        with(LocalDensity.current) { historyHeightPx.toDp() }
                     )
     )
}

Full code:

            var historyVisibility by remember { mutableStateOf(false) }

            TestLayout(
                isShowingHistory = historyVisibility,
                showHistory = {
                    historyVisibility = true
                }, hideHistory = {
                    historyVisibility = false
                })
@Composable
fun TestLayout(
    isShowingHistory: Boolean,
    showHistory: () -> Unit,
    hideHistory: () -> Unit,
) {

    var historyHeightPx by remember { mutableIntStateOf(0) }

    Box(modifier = Modifier
        .fillMaxWidth()
        .onGloballyPositioned {
            val rect = it.boundsInParent()
            //Lets' say you want the history takes 70% of the height
            historyHeightPx = (rect.bottom * 0.7F).toInt()
        }) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Bottom
        ) {

            AnimatedVisibility(visible = isShowingHistory) {
                History(modifier = Modifier
                    .height(with(LocalDensity.current) { historyHeightPx.toDp() })
                )
            }

            InputResult(isCompact = isShowingHistory)
        }

        HistoryIconButton(modifier = Modifier.align(alignment = Alignment.BottomEnd), onClick = {
            if (isShowingHistory) hideHistory() else showHistory()
        })
    }
}

History and InputResult boxes:

@Composable
fun ColumnScope.History(modifier: Modifier) {
    Box(
        modifier = modifier
            .background(color = Color.Gray)
            .fillMaxWidth(),
        contentAlignment = Alignment.BottomEnd
    ) {
        Text(text = "History here")
    }
}

@Composable
fun ColumnScope.InputResult(isCompact: Boolean) {
    Box(
        modifier = Modifier
            .background(color = Color.Blue)
            .fillMaxWidth()
            .weight(1f),
        contentAlignment = Alignment.TopEnd
    ) {
        Text(text = "Inputs here")
    }
}
Dmitri Chernysh
  • 260
  • 1
  • 7
  • hi. thanks for answering. however, I actually don't want the history to take a specific percentage of space. I want it to fill the remaining space left by the input. the same thing with the input. I want it to fill the max size of the parent, then shrink to only wrap its contents when showing the history. that's why my first instict is using weights, but idk how to animate weights – hamthenormal Aug 07 '23 at 06:03