daniyelp
daniyelp

Reputation: 1255

Jetpack Compose - Cannot animate Scaffold's bottom bar's height

I need to reveal some floating action buttons at the bottom of the screen when some other buttons, in a scrollable list, are no longer visible on the screen. Naturally, I feel like using LazyColumn, AnimatedVisibility and Scaffold's bottom bar for this (I need the snackbar and the screen's content be above the floating action buttons). But the animation doesn't work as expected. As the floating buttons appear/dissapear, the bottom bar's height is not animated (but I think it could actually be that the content padding isn't animated).

The result I'm getting:

enter image description here

The code:

val lazyListState = rememberLazyListState()
val isFloatingButtonVisible by derivedStateOf {
    lazyListState.firstVisibleItemIndex >= 2
}
Scaffold(
    bottomBar = {
        AnimatedVisibility(
            visible = isFloatingButtonVisible,
            enter = slideInVertically { height -> height },
            exit = slideOutVertically { height -> height }
        ) {
            Row(
                modifier = Modifier.padding(8.dp),
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                repeat(2) {
                    Button(
                        modifier = Modifier
                            .height(48.dp)
                            .weight(1f),
                        onClick = {}
                    ) {
                        Text(text = "Something")
                    }
                }
            }
        }
    }
) { contentPadding ->
    LazyColumn(
        modifier = Modifier.padding(contentPadding),
        state = lazyListState,
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        item {
            Text(
                text = LoremIpsum(50).values.joinToString(" ")
            )
        }
        item {
            Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                repeat(2) {
                    Button(onClick = {}) {
                        Text(text = "Bla Bla")
                    }
                }
            }
        }
        item {
            Text(
                text = LoremIpsum(700).values.joinToString(" ")
            )
        }
    }
}

Upvotes: 3

Views: 2200

Answers (3)

daniyelp
daniyelp

Reputation: 1255

Got the right result using AnimatedContent instead of AnimatedVisibility.

enter image description here

val lazyListState = rememberLazyListState()
val isFloatingButtonVisible by derivedStateOf {
    lazyListState.firstVisibleItemIndex >= 2
}
Scaffold(
    bottomBar = {
        AnimatedContent(
            targetState = isFloatingButtonVisible,
            transitionSpec = {
                slideInVertically { height -> height } with
                slideOutVertically { height -> height }
            }
        ) { isVisible ->
            if (isVisible) {
                Row(
                    modifier = Modifier
                        .border(1.dp, Color.Red)
                        .padding(8.dp),
                    horizontalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    repeat(2) {
                        Button(
                            modifier = Modifier
                                .height(48.dp)
                                .weight(1f),
                            onClick = {}
                        ) {
                            Text(text = "Something")
                        }
                    }
                }
            } else {
                Box(modifier = Modifier.fillMaxWidth())
            }
        }
    }
) { contentPadding ->
    LazyColumn(
        modifier = Modifier.padding(contentPadding),
        state = lazyListState,
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        item {
            Text(
                text = LoremIpsum(50).values.joinToString(" ")
            )
        }
        item {
            Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                repeat(2) {
                    Button(onClick = {}) {
                        Text(text = "Bla Bla")
                    }
                }
            }
        }
        item {
            Text(
                text = LoremIpsum(700).values.joinToString(" ")
            )
        }
    }
}

Upvotes: 5

vitidev
vitidev

Reputation: 980

I don't think it's possible to do this with AnimatedVisibility. I got it like this

@Composable
fun Test() {
    val lazyListState = rememberLazyListState()

    val isFloatingButtonVisible by remember {
        derivedStateOf {
            lazyListState.firstVisibleItemIndex >= 1
        }
    }

    var barHeight by remember { mutableStateOf(0.dp) }


    var listSizeDelta by remember { mutableStateOf(0.dp) }
    val bottomBarOffset = remember { Animatable(barHeight, Dp.VectorConverter) }

    LaunchedEffect(isFloatingButtonVisible) {
        if (isFloatingButtonVisible) {
            bottomBarOffset.animateTo(barHeight)
            listSizeDelta = barHeight
        } else {
            listSizeDelta = 0.dp
            bottomBarOffset.animateTo(0.dp)
        }
    }

    val density = LocalDensity.current

    BoxWithConstraints(Modifier.fillMaxSize()) {
        LazyColumn(
            modifier = Modifier
                .size(width = maxWidth, height = maxHeight - listSizeDelta)
                .background(Color.Yellow),
            state = lazyListState,
            contentPadding = PaddingValues(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            repeat(15) {
                item {
                    Text(
                        text = LoremIpsum(50).values.joinToString(" ")
                    )
                }
            }
        }

        Row(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .graphicsLayer {
                    translationY =
                        density.run { (-bottomBarOffset.value + barHeight).toPx() }
                }
                .background(Color.Green)
                .onSizeChanged {
                    barHeight = density.run { it.height.toDp()}
                }
                .padding(8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            repeat(2) {
                Button(
                    modifier = Modifier
                        .height(48.dp)
                        .weight(1f),
                    onClick = {}
                ) {
                    Text(text = "Something")
                }
            }
        }
    }
}

Upvotes: 1

commandiron
commandiron

Reputation: 1473

The content padding in lazycolumn modifier breaks the animation;

LazyColumn(
   modifier = Modifier.padding(contentPadding) // -> Delete this.
)

For the same view;

Row(
   modifier = Modifier
      .background(Color.White) //-> Add this.
      .padding(8.dp) 
)

Upvotes: -1

Related Questions