onebone/compose-collapsing-toolbar

CollapsingToolbarScaffoldState.toolbarState.progress changes without scrolling

Opened this issue · 6 comments

CollapsingToolbarScaffoldState.toolbarState.progress value changes when you go back in navigation stack even though no scrolling is happening. If toolbar title size depends on progress value, it visibly changes from smallest to largest when navigating back to screen.
It seems that when navigating back, two progress values are dispatched, 0.0f and then 1.0f.

Can you provide a minimal code snippet to help me reproduce your issue?

I am using AnimatedNavHost from accompanist with no animation when switching between destinations. Regular NavHost with fade animation actually hides text size change, but adding breakpoint to text size calculation you can see that progress is dispatched multiple times without scroll

    @OptIn(ExperimentalAnimationApi::class)
    @Composable
    private fun Test() {
        val navController = rememberNavController()
        Column(modifier = Modifier.fillMaxSize()) {
            NavHost(
                navController = navController,
                startDestination = "first",
                modifier = Modifier.fillMaxSize().weight(1f)
            ) {
                composable("first"
                ) { FirstScreen() }
                composable("second") { SecondScreen() }
            }

            BottomNav(navController = navController)
        }
    }
    
    @Composable
    private fun BottomNav(navController: NavHostController) {
        BottomNavigation() {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentDestination = navBackStackEntry?.destination

            BottomNavigationItem(
                icon = { Icon(Icons.Default.Home, contentDescription = null) },
                label = { Text("First") },
                selected = currentDestination?.hierarchy?.any { it.route == "first" } == true,
                onClick = {
                    navController.navigate("first") {
                        popUpTo(navController.graph.findStartDestination().id) {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )

            BottomNavigationItem(
                icon = { Icon(Icons.Default.Home, contentDescription = null) },
                label = { Text("Second") },
                selected = currentDestination?.hierarchy?.any { it.route == "second" } == true,
                onClick = {
                    navController.navigate("second") {
                        popUpTo(navController.graph.findStartDestination().id) {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    }

    @Composable
    private fun FirstScreen() {
        val state = rememberCollapsingToolbarScaffoldState()
        CollapsingToolbarScaffold(
            modifier = Modifier
                .fillMaxSize(),
            state = state,
            scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
            toolbar = {
                val textSize = remember(state.toolbarState.progress) {
                    (22 + (34 - 22) * state.toolbarState.progress).sp
                }

                Box(
                    modifier = Modifier
                        .background(Color.Gray)
                        .fillMaxWidth()
                        .height(120.dp)
                        .pin()
                )

                Text(
                    text = "First",
                    fontSize = textSize,
                    modifier = Modifier
                        .road(Alignment.CenterStart, Alignment.BottomStart)
                        .padding(
                            start = 16.dp,
                            end = 16.dp,
                            top = 18.dp,
                            bottom = 18.dp
                        ),
                )
            }
        ) {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxSize()
                    .verticalScroll(rememberScrollState())
            ) {
                Text(text = "First screen")
            }
        }
    }

    @Composable
    private fun SecondScreen() {
        val state = rememberCollapsingToolbarScaffoldState()
        CollapsingToolbarScaffold(
            modifier = Modifier
                .fillMaxSize(),
            state = state,
            scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
            toolbar = {
                val textSize = remember(state.toolbarState.progress) {
                    (22 + (34 - 22) * state.toolbarState.progress).sp
                }

                Box(
                    modifier = Modifier
                        .background(Color.Gray)
                        .fillMaxWidth()
                        .height(120.dp)
                        .pin()
                )

                Text(
                    text = "Second",
                    fontSize = textSize,
                    modifier = Modifier
                        .road(Alignment.CenterStart, Alignment.BottomStart)
                        .padding(
                            start = 16.dp,
                            end = 16.dp,
                            top = 18.dp,
                            bottom = 18.dp
                        ),
                )
            }
        ) {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxSize()
                    .verticalScroll(rememberScrollState())
            ) {
                Text(text = "Second screen")
            }
        }
    }

I can confirm the issue, also seeing this when navigating back from a detail screen to a screen with expanded toolbar and there the title flickers (first is small and then big), because its font size depends on progress.

Could narrow it down to the height being the initial value (maximum int value) and thus progress being near 0 (because height is in the denominator for calculating progress).

@onebone any update on this?

I can confirm the issue and looks like its cause is about timing as @ChristopherKlammt said. As a workaround, I added minHeight, maxHeight to the rememerSaveable and it stopped being dispatched twice. It might be relevant to the fact that the composition at the very first time doesn't dispatch textSize change twice?

@onebone can you share the code for that?

Edit: I looked into it and created a PR for that change, is that how you did it as well?