4

I looked at many questions about collapsing of Top App Bar in jetpack compose android e.g. Jetpack Compose collapsing toolbar,Plants details view, etc. But none of the tutorials/examples are using the Scaffold. Hence this question.

Following the example given in the android documentation, I changed it a bit to use it with a scaffold.

// here we use LazyColumn that has a build-in nested scroll, but we want to act like a
// parent for this LazyColumn and participate in its nested scroll.
// Let's make a collapsing toolbar for LazyColumn
val toolbarHeight = 48.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.roundToPx().toFloat() }
// our offset to collapse toolbar
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// now, let's create connection to the nested scroll system and listen to the scroll
// happening inside child LazyColumn
val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            // try to consume before LazyColumn to collapse toolbar if needed, hence pre-scroll
            val delta = available.y
            val newOffset = toolbarOffsetHeightPx.value + delta
            toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
            // here's the catch: let's pretend we consumed 0 in any case, since we want
            // LazyColumn to scroll anyway for good UX
            // We're basically watching scroll without taking it
            return Offset.Zero
        }
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            modifier = Modifier
                .height(toolbarHeight)
                .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) },
            title = { Text("toolbar offset is ${toolbarOffsetHeightPx.value}") }
        )
    }
) {
    Box(
        Modifier
            .fillMaxSize()
            // attach as a parent to the nested scroll system
            .nestedScroll(nestedScrollConnection)
    ) {
        // our list with build in nested scroll support that will notify us about its scroll
        LazyColumn {
            items(100) { index ->
                Text("I'm item $index", modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp))
            }
        }
    }
}

The output is NOT as expected. The Top App Bar tends to move up but the height of the TopBar in the scaffold remains the same and so the list starts to disappear at the height of the Top App Bar.

So my question is how to calculate the height of the top app bar so that it changes according to the offset.

I tried

height - y offset value

But that does not work.

Nik
  • 2,913
  • 7
  • 40
  • 66
  • Does this answer your question? [Jetpack Compose collapsing toolbar](https://stackoverflow.com/questions/67227755/jetpack-compose-collapsing-toolbar) – nglauber Aug 01 '22 at 13:53
  • No, it does not answer my question as the solutions suggested in that question are either using Material design 3 or a third-party lib. It is also not using scaffold anywhere as that's my requirement. – Nik Aug 01 '22 at 15:23

2 Answers2

4

It could be more ability by using Modifier.nestedScroll and scrollBehavior with Scaffold Compose.

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun TopBarton() {
    val appBarState = rememberTopAppBarState()
    val scrollBehavior = remember { TopAppBarDefaults.enterAlwaysScrollBehavior(appBarState) }
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            SmallTopAppBar(
                title = { Text("TopBarton") },
                navigationIcon = {
                    IconButton(onClick = { /* ... */ }) {
                        Icon(
                            imageVector = Icons.Filled.Menu,
                            contentDescription = null
                        )
                    }
                },
                actions = {
                    IconButton(onClick = { /* ... */ }) {
                        Icon(
                            imageVector = Icons.Filled.Star,
                            contentDescription = null
                        )
                    }
                },
                scrollBehavior = scrollBehavior
            )
        },
        content = { innerPadding ->
            LazyColumn(
                contentPadding = innerPadding,
                verticalArrangement = Arrangement.spacedBy(9.dp)
            ) {
                val list = (0..100).map { it.toString() }
                items(count = list.size) {
                    Text(
                        text = list[it],
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(horizontal = 18.dp)
                    )
                }
            }
        }
    )
}
Youn Tivoli
  • 230
  • 2
  • 13
  • 2
    i'm getting "Composable calls are not allowed inside the calculation parameter of inline fun remember(crossinline calculation: () -> TypeVariable(T)): TypeVariable(T)" – Gray May 24 '23 at 23:20
1

Actually, the Scaffold does not make much difference in the answer that I mentioned in the comment above. See the code below.

You just need to adjust for your needs...

@ExperimentalFoundationApi
@Composable
fun CollapsingEffectScreen() {
    val items = (1..100).map { "Item $it" }
    val lazyListState = rememberLazyListState()
    var scrolledY = 0f
    var previousOffset = 0
    Scaffold {
        LazyColumn(
            Modifier
                .fillMaxSize()
                .padding(it),
            lazyListState,
        ) {
            item {
                TopAppBar(
                    Modifier
                        .graphicsLayer {
                            scrolledY += lazyListState.firstVisibleItemScrollOffset - previousOffset
                            translationY = scrolledY * 0.5f
                            previousOffset = lazyListState.firstVisibleItemScrollOffset
                        }
                        .height(240.dp)
                        .fillMaxWidth()
                ) {
                    Image(
                        modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                        painter = painterResource(id = R.drawable.recife),
                        contentDescription = null,
                        contentScale = ContentScale.FillWidth,
                    )
                }
            }
            items(items) {
                Text(
                    text = it,
                    Modifier
                        .background(MaterialTheme.colors.surface)
                        .fillMaxWidth()
                        .padding(8.dp)
                )
            }
        }
    }
}
nglauber
  • 18,674
  • 6
  • 70
  • 75
  • 2nThank you for sharing the code with scaffold implementation @nglauber. There is a major difference between what you are doing and what I am looking for... I am using the `topBar` parameter from the scaffold to define the TopAppBar. Keeping the height constant will move the contents of the toolbar using offset but because the height is constant, the content scrolling starts disappearing at the height of the toolbar. You can try my code and understand it better. – Nik Aug 04 '22 at 08:08
  • Perhaps you know how to fix my issue, could you take a look please? https://stackoverflow.com/questions/76326733/collapsingtoolbar-in-compose-with-button-sticky – StuartDTO May 29 '23 at 13:30