0

I have been trying implementing the collapsible toolbar in my app. I have the following composables

 ModalNavigationDrawer(
    drawerContent = {
        ModalDrawerSheet(drawerContainerColor = Color.White) {
            // my menu items
        }

    }, drawerState = drawerState, content = {  }, modifier = Modifier
        .background(
            colorResource(id = R.color.white)
        )
){
   Column(
                modifier = Modifier
                    .verticalScroll(rememberScrollState())
                    .background(colorResource(id = R.color.white))
                    .fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                HeaderTile("Good Morning", coroutineScope, drawerState);
                OptionsTile(
                    modifier = Modifier.fillMaxWidth(),
                )
            }
 }

 @Composable
 fun HeaderTile(
    greetMessage: String,
    coroutineScope: CoroutineScope,
    drawerState: DrawerState
) {
    val transparentRed = Color.Red.copy(alpha = 0.8f) // 50% transparent red
    val transparentBlue = Color.Black.copy(alpha = 0.8f)
    val brush = Brush.verticalGradient(listOf(transparentRed, transparentBlue))
    Box(
        modifier = Modifier
            .height(240.dp)
            .fillMaxWidth(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.header_image),
            contentDescription = null,
            contentScale = ContentScale.Crop, // Adjust as needed
            modifier = Modifier.fillMaxSize()
        )
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = brush
                ) // Bottom-right corner
        )
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.TopStart)
                .padding(top = 5.dp)
                .wrapContentHeight(),
            contentAlignment = Alignment.TopStart
        ) {
            androidx.compose.material3.IconButton(onClick = { coroutineScope.launch { drawerState.open() } },
                content = {
                    androidx.compose.material3.Icon(
                        imageVector = Icons.Default.Menu, contentDescription = null,
                        Modifier.size(30.dp),
                        tint = colorResource(id = R.color.white)
                    )
                })
            Column(
                modifier = Modifier
                    .fillMaxWidth(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                androidx.compose.material3.Text(
                    text = "#Hello Everyone",
                    color = White,
                    style = androidx.compose.material3.MaterialTheme.typography.headlineSmall,
                    fontStyle = FontStyle.Italic,
                )
            }
        }
    }
}


@Composable
fun OptionsTile(
modifier: Modifier = Modifier
) {
Row(
    modifier = modifier
        .offset(y = -(50).dp)
        .padding(start = 15.dp, end = 15.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
    for (i in 1..3) {
        Card(
            colors = CardDefaults.cardColors(
                containerColor = White
            ),
            modifier = Modifier
                .height(height = 100.dp)
                .weight(1f),
            elevation = CardDefaults.cardElevation(defaultElevation = 5.dp),
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {

            }
        }
    }
}

}

This will give me layout like this

enter image description here

I want when scrolling up, red image header should collapse upto to three menu options like this

enter image description here

Any Suggestions !

1

1 Answer 1

1
+50

You can use a NestedScrollConnection to achieve this. Override the onPreScroll function, and when the header is not fully expanded / collapsed, consume the scroll event to increase / decrease the height of the header.

Output:

Screen Recording

Code:

@Composable
fun MainComposable() {

    val coroutineScope = rememberCoroutineScope()

    val density = LocalDensity.current

    var minHeaderHeightPx by remember { mutableFloatStateOf(100.dp.dpToPx(density)) }
    var maxHeaderHeightPx by remember { mutableFloatStateOf(250.dp.dpToPx(density)) }
    var currentHeaderHeightPx by remember { mutableFloatStateOf(250.dp.dpToPx(density)) }

    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val delta = available.y
                val newHeaderHeightPx = currentHeaderHeightPx + delta
                currentHeaderHeightPx = newHeaderHeightPx.coerceIn(minHeaderHeightPx, maxHeaderHeightPx)
                val unconsumedPx = newHeaderHeightPx - currentHeaderHeightPx
                return Offset(x = 0f, y = delta - unconsumedPx)
            }
        }
    }

    Column(
        modifier = Modifier
            .background(colorResource(id = R.color.white))
            .fillMaxSize()
            .nestedScroll(nestedScrollConnection),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        HeaderTile(
            modifier = Modifier
                .height(currentHeaderHeightPx.toInt().pxToDp(density)),
            greetMessage = "Good Morning",
            expansionProgress = currentHeaderHeightPx / maxHeaderHeightPx,
            coroutineScope = coroutineScope
        );
        Spacer(modifier = Modifier.height(56.dp))
        LazyColumn(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
        ) {
            items(50) { item ->
                Text("Item $item", modifier = Modifier.padding(16.dp))
            }
        }
    }
}

@Composable
fun HeaderTile(
    modifier: Modifier = Modifier,
    greetMessage: String,
    expansionProgress: Float,
    coroutineScope: CoroutineScope,
) {
    val transparentRed = Color.Red.copy(alpha = 0.8f) // 50% transparent red
    val transparentBlue = Color.Black.copy(alpha = 0.8f)
    val brush = Brush.verticalGradient(listOf(transparentRed, transparentBlue))
    Box(
        modifier = modifier
            .fillMaxWidth(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            imageVector = Icons.Filled.LocalPolice,
            contentDescription = null,
            contentScale = ContentScale.Crop, // Adjust as needed
            modifier = Modifier.fillMaxSize()
        )
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = brush
                ) // Bottom-right corner
        )
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.TopStart)
                .padding(top = 5.dp)
                .wrapContentHeight(),
            contentAlignment = Alignment.TopStart
        ) {
            IconButton(onClick = {},
                content = {
                    Icon(
                        imageVector = Icons.Default.Menu, contentDescription = null,
                        Modifier.size(30.dp),
                        tint = colorResource(id = R.color.white)
                    )
                }
            )
        }
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.Center)
                .offset(x = 0.dp, y = -(32 * (1 - expansionProgress)).dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                modifier = Modifier.padding(8.dp),
                text = greetMessage,
                color = White,
                style = MaterialTheme.typography.headlineSmall,
                fontStyle = FontStyle.Italic,
            )
        }
        OptionsTile(
            modifier = Modifier.align(Alignment.BottomCenter)
        )
    }
}

@Composable
fun OptionsTile(
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
            .offset(y = 50.dp)
            .padding(start = 15.dp, end = 15.dp)
            .fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        for (i in 1..3) {
            Card(
                colors = CardDefaults.cardColors(
                    containerColor = White,
                ),
                modifier = Modifier
                    .height(height = 100.dp)
                    .weight(1f),
                elevation = CardDefaults.cardElevation(defaultElevation = 5.dp),
            ) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {

                }
            }
        }
    }
}

fun Dp.dpToPx(density: Density) = with(density) { [email protected]() }
fun Int.pxToDp(density: Density) = with(density) { [email protected]() }

Note:

I removed some redundant code related to the ModalNavigationDrawer.

Not the answer you're looking for? Browse other questions tagged or ask your own question.