Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to implement this Bottomsheet using Jetpack Compose?

the default ModalBottomsheet is not give me a flexibility to customize the UI to look like the UI in the screenshot enter image description here

like image 778
kururu Avatar asked Aug 31 '26 23:08

kururu


1 Answers

You can customize the ModalBottomSheet to match the UI from the picture, and here is an example:


Firstly, add the following parameters to the BottomSheet composable function:

  • isBottomSheetVisible: Boolean - Used for controlling the visibility of the bottom sheet.
  • sheetState: SheetState - The state of the bottom sheet.
  • onDismiss: () -> Unit - The callback to dismiss the bottom sheet.

Then, wrap the ModalBottomSheet inside if (isBottomSheetVisible) {} block and change the following sheet properties:

  • onDismissRequest to onDismiss.
  • sheetState to sheetState.
  • containerColor to Color.Transparent.
  • contentColor to your content color e.g. a text color.
  • shape to a RectangleShape.
  • dragHandle to null
  • scrimColor to the scrim color of the design (e.g. Color.Black.copy(alpha = .5f)).
  • And optionally set the windowInsets to WindowInsets(0, 0, 0, 0) if you have Edge-To-Edge enabled, to expand the bottom sheet under the system bars.

So far, the BottomSheet composable should look like this:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheet(
    isBottomSheetVisible: Boolean,
    sheetState: SheetState,
    onDismiss: () -> Unit
) {

    if (isBottomSheetVisible) {

        ModalBottomSheet(
            onDismissRequest = onDismiss,
            sheetState = sheetState,
            containerColor = Color.Transparent,
            contentColor = MaterialTheme.colorScheme.onSurface,
            shape = RectangleShape,
            dragHandle = null,
            scrimColor = Color.Black.copy(alpha = .5f),
            windowInsets = WindowInsets(0, 0, 0, 0)
        ) {

            // Implement the custom layout here ...

        }

    }

}

Now, you can start implementing the custom layout inside the content block of the ModalBottomSheet:

  • The centered button for dismissing the ModalBottomSheet can be wrapped inside a Box with a bit of padding and customizing it further:
Box(
    modifier = Modifier
        .statusBarsPadding()
        .fillMaxWidth()
        .padding(8.dp),
    contentAlignment = Alignment.Center
) {

    FilledIconButton(
        modifier = Modifier.size(48.dp),
        onClick = onDismiss,
        colors = IconButtonDefaults.filledIconButtonColors(
            containerColor = MaterialTheme.colorScheme.background
        )
    ) {

        Icon(
            imageVector = Icons.Rounded.Close,
            contentDescription = "Dismiss the dialog."
        )

    }

}
  • Then, below the Box add a Column, and implement the remaining UI components:
Column(
    modifier = Modifier
        .navigationBarsPadding()
        .padding(12.dp) // Outer padding
        .clip(shape = RoundedCornerShape(24.dp))
        .background(color = MaterialTheme.colorScheme.background)
        .fillMaxWidth()
        .padding(24.dp) // Inner padding
) {

    Spacer(modifier = Modifier.height(16.dp))

    OutlinedTextField(
        modifier = Modifier.fillMaxWidth(),
        value = "10th avenue, Some, State",
        onValueChange = {},
        label = { Text(text = "Delivery Address") },
        readOnly = true,
        shape = RoundedCornerShape(12.dp)
    )

    Spacer(modifier = Modifier.height(24.dp))

    OutlinedTextField(
        modifier = Modifier.fillMaxWidth(),
        value = "09090606000",
        onValueChange = {},
        label = { Text(text = "Number we can call") },
        readOnly = true,
        shape = RoundedCornerShape(12.dp)
    )

    Spacer(modifier = Modifier.height(48.dp))

    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceBetween
    ) {

        OutlinedButton(
            onClick = {},
            content = { Text(text = "Pay on delivery") }
        )

        OutlinedButton(
            onClick = {},
            content = { Text(text = "Pay with card") }
        )

    }

}

Finally, wherever you need the bottom sheet, declare the following variables:

  • scope - A coroutine scope used to efficiently expand or hide the bottom sheet.
  • isBottomSheetVisible - Used for controlling the bottom sheet visibility.
  • sheetState - The state of the bottom sheet.

Then, implement the BottomSheet composable:

val scope = rememberCoroutineScope()
var isBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(
    skipPartiallyExpanded = true
)

BottomSheet(
    isBottomSheetVisible = isBottomSheetVisible,
    sheetState = sheetState,
    onDismiss = {
        scope.launch { sheetState.hide() }
            .invokeOnCompletion { isBottomSheetVisible = false }
    }
)

This is the full code that I've wrote and a visual demonstration:

class MainActivity : ComponentActivity() {

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {

                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {

                    val scope = rememberCoroutineScope()
                    var isBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
                    val sheetState = rememberModalBottomSheetState(
                        skipPartiallyExpanded = true
                    )

                    // A box with a simple button
                    // just to show the bottom sheet.
                    Box(
                        modifier = Modifier
                            .fillMaxSize()
                            .systemBarsPadding()
                            .padding(64.dp),
                        contentAlignment = Alignment.BottomCenter
                    ) {

                        OutlinedButton(
                            onClick = {
                                scope.launch {
                                    isBottomSheetVisible = true
                                    sheetState.expand()
                                }
                            }
                        ) {
                            Text(text = "Show")
                        }

                    }

                    BottomSheet(
                        isBottomSheetVisible = isBottomSheetVisible,
                        sheetState = sheetState,
                        onDismiss = {
                            scope.launch { sheetState.hide() }
                                .invokeOnCompletion { isBottomSheetVisible = false }
                        }
                    )

                }
            }
        }
    }

}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheet(
    isBottomSheetVisible: Boolean,
    sheetState: SheetState,
    onDismiss: () -> Unit
) {

    if (isBottomSheetVisible) {

        ModalBottomSheet(
            onDismissRequest = onDismiss,
            sheetState = sheetState,
            containerColor = Color.Transparent,
            contentColor = MaterialTheme.colorScheme.onSurface,
            shape = RectangleShape,
            dragHandle = null,
            scrimColor = Color.Black.copy(alpha = .5f),
            windowInsets = WindowInsets(0, 0, 0, 0)
        ) {

            Box(
                modifier = Modifier
                    .statusBarsPadding()
                    .fillMaxWidth()
                    .padding(8.dp),
                contentAlignment = Alignment.Center
            ) {

                FilledIconButton(
                    modifier = Modifier.size(48.dp),
                    onClick = onDismiss,
                    colors = IconButtonDefaults.filledIconButtonColors(
                        containerColor = MaterialTheme.colorScheme.background
                    )
                ) {

                    Icon(
                        imageVector = Icons.Rounded.Close,
                        contentDescription = "Hide the dialog."
                    )

                }

            }

            Column(
                modifier = Modifier
                    .navigationBarsPadding()
                    .padding(12.dp) // Outer padding
                    .clip(shape = RoundedCornerShape(24.dp))
                    .background(color = MaterialTheme.colorScheme.background)
                    .fillMaxWidth()
                    .padding(24.dp) // Inner padding
            ) {

                Spacer(modifier = Modifier.height(16.dp))

                OutlinedTextField(
                    modifier = Modifier.fillMaxWidth(),
                    value = "10th avenue, Some, State",
                    onValueChange = {},
                    label = { Text(text = "Delivery Address") },
                    readOnly = true,
                    shape = RoundedCornerShape(12.dp)
                )

                Spacer(modifier = Modifier.height(24.dp))

                OutlinedTextField(
                    modifier = Modifier.fillMaxWidth(),
                    value = "09090606000",
                    onValueChange = {},
                    label = { Text(text = "Number we can call") },
                    readOnly = true,
                    shape = RoundedCornerShape(12.dp)
                )

                Spacer(modifier = Modifier.height(48.dp))

                Row(
                    modifier = Modifier.fillMaxWidth(),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.SpaceBetween
                ) {

                    OutlinedButton(
                        onClick = {},
                        content = { Text(text = "Pay on delivery") }
                    )

                    OutlinedButton(
                        onClick = {},
                        content = { Text(text = "Pay with card") }
                    )

                }

            }

        }

    }

}

A visual demonstration of the custom modal bottom sheet.

Happy coding! 🙌🏼

like image 151
stoyan_vuchev Avatar answered Sep 02 '26 14:09

stoyan_vuchev