Getting Started
If you created your project with Empty Compose Activity
template, you can skip this part but don’t forget to check versions and if necessary, update them. How to create Compose app?
//... | |
android { | |
//... | |
buildFeatures { | |
compose true | |
} | |
composeOptions { | |
kotlinCompilerExtensionVersion '1.3.1' | |
} | |
} | |
dependencies { | |
//... | |
def compose_ui_version = '1.3.1' | |
implementation "androidx.compose.ui:ui:$compose_ui_version" | |
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" | |
implementation "androidx.compose.material:material:$compose_ui_version" | |
} |
Implementation
First, we set our state variable and coroutine scope.
val coroutineScope = rememberCoroutineScope() | |
val modalSheetState = rememberModalBottomSheetState( | |
initialValue = ModalBottomSheetValue.Hidden, | |
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded }, | |
skipHalfExpanded = true, | |
) |
confirmStateChange
is necessary to control the behavior of bottom sheet. If you set confirmStateChange = { false }
user will not be able to dismiss bottom sheet by dragging or tapping outside.
skipHalfExpanded
Whether the half expanded state, if the sheet is tall enough, should be skipped. If true, the sheet will always expand to theExpanded state and move to the
Hidden state when hiding the sheet, either programmatically or by user interaction.
Now we will create our bottom sheet UI,
ModalBottomSheetLayout( | |
sheetState = modalSheetState, | |
sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp), | |
sheetContent = { | |
Column( | |
//... | |
) { | |
//... | |
Button( | |
onClick = { | |
coroutineScope.launch { modalSheetState.hide() } | |
} | |
) { | |
Text(text = "Hide Sheet") | |
} | |
} | |
} | |
) { | |
Scaffold { | |
Box( | |
//... | |
) { | |
Button( | |
onClick = { | |
coroutineScope.launch { | |
if (modalSheetState.isVisible) | |
modalSheetState.hide() | |
else | |
modalSheetState.animateTo(ModalBottomSheetValue.Expanded) | |
} | |
}, | |
) { | |
Text(text = "Open Sheet") | |
} | |
} | |
} | |
} |
sheetShape
is optional and totally up to you. I like to add it to give more rounded look to the bottom sheet.
sheetContent
is where you put your bottom sheet UI. In our example we have Button
which dismisses the bottom sheet on click.
As we can see from the example, we use coroutineScope
to change bottom sheet state since modalSheetState.hide
and modalSheetState.animateTo
are suspended
functions.
Finally, inside of our Scaffold
we have Button
which expands bottom sheet on click.
Thats it! It’s very easy to create and manage bottom sheet with Jetpack Compose.
@Composable | |
fun BottomSheetLayout() { | |
val coroutineScope = rememberCoroutineScope() | |
val modalSheetState = rememberModalBottomSheetState( | |
initialValue = ModalBottomSheetValue.Hidden, | |
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded }, | |
skipHalfExpanded = true | |
) | |
ModalBottomSheetLayout( | |
sheetState = modalSheetState, | |
sheetContent = { | |
Column( | |
//... | |
) { | |
Button( | |
onClick = { | |
coroutineScope.launch { modalSheetState.hide() } | |
} | |
) { | |
Text(text = "Hide Sheet") | |
} | |
} | |
} | |
) { | |
Scaffold { | |
Box( | |
modifier = Modifier | |
.fillMaxSize(), | |
contentAlignment = Alignment.Center, | |
) { | |
Button( | |
onClick = { | |
coroutineScope.launch { | |
if (modalSheetState.isVisible) | |
modalSheetState.hide() | |
else | |
modalSheetState.animateTo(ModalBottomSheetValue.Expanded) | |
} | |
}, | |
) { | |
Text(text = "Open Sheet") | |
} | |
} | |
} | |
} | |
} |
Job Offers
Extras
Dismiss On Back
BackHandler(modalSheetState.isVisible) { | |
coroutineScope.launch { modalSheetState.hide() } | |
} |
With this simple code, we can close bottom sheet on back button press if visible.
Toggle Between Fullscreen & Expanded
@Composable | |
fun BottomSheetLayout() { | |
//... | |
var isSheetFullScreen by remember { mutableStateOf(false) } | |
val roundedCornerRadius = if (isSheetFullScreen) 0.dp else 12.dp | |
val modifier = if (isSheetFullScreen) | |
Modifier | |
.fillMaxSize() | |
else | |
Modifier.fillMaxWidth() | |
ModalBottomSheetLayout( | |
sheetShape = RoundedCornerShape(topStart = roundedCornerRadius, topEnd = roundedCornerRadius), | |
sheetContent = { | |
Column( | |
modifier = modifier, | |
//... | |
) { | |
Button( | |
onClick = { | |
isSheetFullScreen = !isSheetFullScreen | |
} | |
) { | |
Text(text = "Toggle Sheet Fullscreen") | |
} | |
} | |
} | |
) { | |
//... | |
} | |
} |
First, we create isSheetFullScreen
state variable and set roundedCornerRadius
and modifier
variables which will change on isSheetFullScreen
state change. e.g., if isSheetFullScreen
is true corner radius will be 0 else it’ll change to 12.
Finally, we are changing the state on Button
click. If you want to present your bottom sheet only full screen, you can delete all states and simply change, Modifier.fillMaxWidth
to Modifier.fillMaxSize
.
Full Code with Extras
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
JetpackComposeBottomSheetTheme { | |
Surface( | |
modifier = Modifier.fillMaxSize(), | |
color = MaterialTheme.colors.background | |
) { | |
BottomSheetLayout() | |
} | |
} | |
} | |
} | |
} | |
@SuppressLint("UnusedMaterialScaffoldPaddingParameter") | |
@OptIn(ExperimentalMaterialApi::class) | |
@Composable | |
fun BottomSheetLayout() { | |
val coroutineScope = rememberCoroutineScope() | |
val modalSheetState = rememberModalBottomSheetState( | |
initialValue = ModalBottomSheetValue.Hidden, | |
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded }, | |
skipHalfExpanded = true | |
) | |
var isSheetFullScreen by remember { mutableStateOf(false) } | |
val roundedCornerRadius = if (isSheetFullScreen) 0.dp else 12.dp | |
val modifier = if (isSheetFullScreen) | |
Modifier | |
.fillMaxSize() | |
else | |
Modifier.fillMaxWidth() | |
BackHandler(modalSheetState.isVisible) { | |
coroutineScope.launch { modalSheetState.hide() } | |
} | |
ModalBottomSheetLayout( | |
sheetState = modalSheetState, | |
sheetShape = RoundedCornerShape(topStart = roundedCornerRadius, topEnd = roundedCornerRadius), | |
sheetContent = { | |
Column( | |
modifier = modifier, | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.Center, | |
) { | |
Button( | |
onClick = { | |
isSheetFullScreen = !isSheetFullScreen | |
} | |
) { | |
Text(text = "Toggle Sheet Fullscreen") | |
} | |
Button( | |
onClick = { | |
coroutineScope.launch { modalSheetState.hide() } | |
} | |
) { | |
Text(text = "Hide Sheet") | |
} | |
} | |
} | |
) { | |
Scaffold { | |
Box( | |
modifier = Modifier | |
.fillMaxSize(), | |
contentAlignment = Alignment.Center, | |
) { | |
Button( | |
onClick = { | |
coroutineScope.launch { | |
if (modalSheetState.isVisible) | |
modalSheetState.hide() | |
else | |
modalSheetState.animateTo(ModalBottomSheetValue.Expanded) | |
} | |
}, | |
) { | |
Text(text = "Open Sheet") | |
} | |
} | |
} | |
} | |
} |
This article was originally published on proandroiddev.com on December 06, 2022