animationandroid-jetpack-composerow

AnimatedVisibility behaves differently based on layout sequence Jetpack Compose


Hi I have simple case that I make visible buttons placed next to a TextField in a Row based on index. I observed that if I put buttons before TextFieldin that row TextField behaves as I'm expected and fills max width when possible. But if I put buttons after TextField then TextField always occupy max width and buttons never be visible. What is the reason for that, and what are the possible solutions ?

Thanks in advance.

Here is the reproducable code snippet and SS:

Case 1:


@Composable
fun UrlTextfield2(
    modifier: Modifier = Modifier,
    isPlusButtonVisible: Boolean,
    isMinusButtonVisible: Boolean,
    onClickPlusButton: () -> Unit,
    onClickMinusButton: () -> Unit,
) {
    Row(modifier = modifier.fillMaxWidth()) {
        AnimatedVisibility(visible = isPlusButtonVisible) {
            IconButton(onClick = onClickPlusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                Icon(imageVector = Icons.Default.Add, contentDescription = "")
            }
        }

        AnimatedVisibility(visible = isMinusButtonVisible) {
            IconButton(onClick = onClickMinusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                Icon(imageVector = Icons.Default.Close, contentDescription = "")
            }
        }

        TextField(
            value = "",
            onValueChange = { },
            modifier = Modifier.fillMaxWidth(),
            label = { Text(text = "URL") },
        )
    }
}



enter image description here

Case 2:

@Composable
fun UrlTextfield2(
    modifier: Modifier = Modifier,
    isPlusButtonVisible: Boolean,
    isMinusButtonVisible: Boolean,
    onClickPlusButton: () -> Unit,
    onClickMinusButton: () -> Unit,
) {
    Row(modifier = modifier.fillMaxWidth()) {
        TextField(
            value = "",
            onValueChange = { },
            modifier = Modifier.fillMaxWidth(),
            label = { Text(text = "URL") },
        )

        AnimatedVisibility(visible = isPlusButtonVisible) {
            IconButton(onClick = onClickPlusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                Icon(imageVector = Icons.Default.Add, contentDescription = "")
            }
        }

        AnimatedVisibility(visible = isMinusButtonVisible) {
            IconButton(onClick = onClickMinusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                Icon(imageVector = Icons.Default.Close, contentDescription = "")
            }
        }
    }
}

enter image description here


Solution

  • The issue isn't related to AnimatedVisibility but to how the fillMaxWidth modifier works.

    In Case 1, the buttons are placed before the TextField. The TextField takes the remaining space with fillMaxWidth(). This works as expected because the TextField fills the remaining width after the buttons are placed.

    In Case 2, the TextField is placed before the buttons. Since the TextField uses fillMaxWidth(), it takes up all the available space, leaving no room for the buttons, which is why they never become visible.

    To fix this, you can use the weight modifier instead of fillMaxWidth. The weight modifier allows the TextField to take up the remaining space after the buttons are given their specified width.

    Here's the corrected code for both cases:

    Case 1:

    @Composable
    fun UrlTextfield2(
        modifier: Modifier = Modifier,
        isPlusButtonVisible: Boolean,
        isMinusButtonVisible: Boolean,
        onClickPlusButton: () -> Unit,
        onClickMinusButton: () -> Unit,
    ) {
        Row(modifier = modifier.fillMaxWidth()) {
            AnimatedVisibility(visible = isPlusButtonVisible) {
                IconButton(onClick = onClickPlusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                    Icon(imageVector = Icons.Default.Add, contentDescription = "")
                }
            }
    
            AnimatedVisibility(visible = isMinusButtonVisible) {
                IconButton(onClick = onClickMinusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                    Icon(imageVector = Icons.Default.Close, contentDescription = "")
                }
            }
    
            TextField(
                value = "",
                onValueChange = { },
                modifier = Modifier.weight(1f),
                label = { Text(text = "URL") },
            )
        }
    }
    

    Case 2:

    @Composable
    fun UrlTextfield2(
        modifier: Modifier = Modifier,
        isPlusButtonVisible: Boolean,
        isMinusButtonVisible: Boolean,
        onClickPlusButton: () -> Unit,
        onClickMinusButton: () -> Unit,
    ) {
        Row(modifier = modifier.fillMaxWidth()) {
            TextField(
                value = "",
                onValueChange = { },
                modifier = Modifier.weight(1f),
                label = { Text(text = "URL") },
            )
    
            AnimatedVisibility(visible = isPlusButtonVisible) {
                IconButton(onClick = onClickPlusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                    Icon(imageVector = Icons.Default.Add, contentDescription = "")
                }
            }
    
            AnimatedVisibility(visible = isMinusButtonVisible) {
                IconButton(onClick = onClickMinusButton, modifier = Modifier.fillMaxWidth(0.1f)) {
                    Icon(imageVector = Icons.Default.Close, contentDescription = "")
                }
            }
        }
    }