Search code examples
kotlinandroid-jetpack-compose

Jetpack Compose FAB hide on scroll behavior


In the XML-based UI, FAB had a field to override the custom scroll behavior. Since there is no such parameter in the function in Compose FAB implementation, it became a little unclear how to implement the hide on scroll behavior.

I use Scaffold with the nestedScroll modifier in order for my СollapsingAppBar to work. I assumed at first that it was necessary to create an object inherited from the NestedScrollConnection interface. And also connect it with the nestedScroll modifier to Scaffold. But unfortunately, as I understand it, it is impossible to connect several scrollBehavior objects to one Scaffold at once.

val scrollBehavior = TopAppBarScrollBehavior()
val scrollBehavior1 = FloatingActionButtonScrollBehavior()

Scaffold(
    modifier = Modifier
        .nestedScroll(scrollBehavior.nestedScrollConnection)
        //this one won't work
        .nestedScroll(scrollBehavior1.nestedScrollConnection)
        ...

Solution

  • If someone is interested in how to implement this behavior, I found this solution:

    @Composable
    fun AnimatedFloatingActionButton(
        ...
        listState: LazyListState
    ) {
        AnimatedVisibility(
            visible = listState.isScrollingUp().value,
            enter = //TODO: your enter animation,
            exit = //TODO: your exit animation
        ) {
            FloatingActionButton(
                ...
            )
        }
    }
    

    You only need to create a LazyListState inside your Composable parent, and pass it inside the AnimatedFloatingActionButton. And also define an extension function for LazyListState.

    @Composable
    fun LazyListState.isScrollingUp(): State<Boolean> {
        return produceState(initialValue = true) {
            var lastIndex = 0
            var lastScroll = Int.MAX_VALUE
            snapshotFlow {
                firstVisibleItemIndex to firstVisibleItemScrollOffset
            }.collect { (currentIndex, currentScroll) ->
                if (currentIndex != lastIndex || currentScroll != lastScroll) {
                    value = currentIndex < lastIndex ||
                            (currentIndex == lastIndex && currentScroll < lastScroll)
                    lastIndex = currentIndex
                    lastScroll = currentScroll
                }
            }
        }
    }