SwipeToDismissBox
Android
Component in Wear Material 3 Compose
Wear Material 3 [SwipeToDismissBox] that handles the swipe-to-dismiss gesture. Takes a single slot for the background (only displayed during the swipe gesture) and the foreground content.
Last updated:
Installation
dependencies {
implementation("androidx.wear.compose:compose-material3:1.0.0-alpha27")
}
Overloads
@Composable
fun SwipeToDismissBox(
state: SwipeToDismissBoxState,
modifier: Modifier = Modifier,
backgroundScrimColor: Color = MaterialTheme.colorScheme.background,
contentScrimColor: Color = MaterialTheme.colorScheme.background,
backgroundKey: Any = SwipeToDismissKeys.Background,
contentKey: Any = SwipeToDismissKeys.Content,
userSwipeEnabled: Boolean = true,
content: @Composable BoxScope.(isBackground: Boolean) -> Unit
)
Parameters
name | description |
---|---|
state | State containing information about ongoing swipe or animation. |
modifier | [Modifier] for this component. |
backgroundScrimColor | [Color] for background scrim. |
contentScrimColor | [Color] used for the scrim over the content composable during the swipe gesture. |
backgroundKey | [key] which identifies the content currently composed in the [content] block when isBackground == true. Provide the backgroundKey if your background content will be displayed as a foreground after the swipe animation ends (as is common when [SwipeToDismissBox] is used for the navigation). This allows remembered state to be correctly moved between background and foreground. |
contentKey | [key] which identifies the content currently composed in the [content] block when isBackground == false. See [backgroundKey]. |
userSwipeEnabled | Whether the swipe gesture is enabled. (e.g. when there is no background screen, set userSwipeEnabled = false) |
content | Slot for content, with the isBackground parameter enabling content to be displayed behind the foreground content - the background is normally hidden, is shown behind a scrim during the swipe gesture, and is shown without scrim once the finger passes the swipe-to-dismiss threshold. |
@Composable
fun SwipeToDismissBox(
onDismissed: () -> Unit,
modifier: Modifier = Modifier,
state: SwipeToDismissBoxState = rememberSwipeToDismissBoxState(),
backgroundScrimColor: Color = MaterialTheme.colorScheme.background,
contentScrimColor: Color = MaterialTheme.colorScheme.background,
backgroundKey: Any = SwipeToDismissKeys.Background,
contentKey: Any = SwipeToDismissKeys.Content,
userSwipeEnabled: Boolean = true,
content: @Composable BoxScope.(isBackground: Boolean) -> Unit
)
Parameters
name | description |
---|---|
onDismissed | Executes when the swipe to dismiss has completed. |
modifier | [Modifier] for this component. |
state | State containing information about ongoing swipe or animation. |
backgroundScrimColor | [Color] for background scrim. |
contentScrimColor | [Color] used for the scrim over the content composable during the swipe gesture. |
backgroundKey | [key] which identifies the content currently composed in the [content] block when isBackground == true. Provide the backgroundKey if your background content will be displayed as a foreground after the swipe animation ends (as is common when [SwipeToDismissBox] is used for the navigation). This allows remembered state to be correctly moved between background and foreground. |
contentKey | [key] which identifies the content currently composed in the [content] block when isBackground == false. See [backgroundKey]. |
userSwipeEnabled | Whether the swipe gesture is enabled. (e.g. when there is no background screen, set userSwipeEnabled = false) |
content | Slot for content, with the isBackground parameter enabling content to be displayed behind the foreground content - the background is normally hidden, is shown behind a scrim during the swipe gesture, and is shown without scrim once the finger passes the swipe-to-dismiss threshold. |
Code Examples
StatefulSwipeToDismissBox
@Composable
fun StatefulSwipeToDismissBox() {
// State for managing a 2-level navigation hierarchy between
// MainScreen and ItemScreen composables.
// Alternatively, use SwipeDismissableNavHost from wear.compose.navigation.
var showMainScreen by remember { mutableStateOf(true) }
val saveableStateHolder = rememberSaveableStateHolder()
// Swipe gesture dismisses ItemScreen to return to MainScreen.
val state = rememberSwipeToDismissBoxState()
LaunchedEffect(state.currentValue) {
if (state.currentValue == SwipeToDismissValue.Dismissed) {
state.snapTo(SwipeToDismissValue.Default)
showMainScreen = !showMainScreen
}
}
// Hierarchy is ListScreen -> ItemScreen, so we show ListScreen as the background behind
// the ItemScreen, otherwise there's no background to show.
SwipeToDismissBox(
state = state,
userSwipeEnabled = !showMainScreen,
backgroundKey = if (!showMainScreen) "MainKey" else "Background",
contentKey = if (showMainScreen) "MainKey" else "ItemKey",
) { isBackground ->
if (isBackground || showMainScreen) {
// Best practice would be to use State Hoisting and leave this composable stateless.
// Here, we want to support MainScreen being shown from different destinations
// (either in the foreground or in the background during swiping) - that can be achieved
// using SaveableStateHolder and rememberSaveable as shown below.
saveableStateHolder.SaveableStateProvider(
key = "MainKey",
content = {
// Composable that maintains its own state
// and can be shown in foreground or background.
val checked = rememberSaveable { mutableStateOf(true) }
Column(
modifier =
Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 8.dp),
verticalArrangement =
Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
) {
Row(
modifier =
Modifier.height(40.dp)
.background(
color = MaterialTheme.colorScheme.surfaceContainer,
shape = CircleShape
)
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.clickable { showMainScreen = false }) {
Text("Item details")
}
CheckboxButton(
label = { Text("Checkbox", maxLines = 1) },
checked = checked.value,
onCheckedChange = { checked.value = it },
)
}
}
}
)
} else {
Column(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primary),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("Show details here...", color = MaterialTheme.colorScheme.onPrimary)
Text("Swipe right to dismiss", color = MaterialTheme.colorScheme.onPrimary)
}
}
}
}
EdgeSwipeForSwipeToDismiss
@Composable
fun EdgeSwipeForSwipeToDismiss(navigateBack: () -> Unit) {
val state = rememberSwipeToDismissBoxState()
// When using Modifier.edgeSwipeToDismiss, it is required that the element on which the
// modifier applies exists within a SwipeToDismissBox which shares the same state.
SwipeToDismissBox(state = state, onDismissed = navigateBack) { isBackground ->
val horizontalScrollState = rememberScrollState(0)
if (isBackground) {
Box(
modifier =
Modifier.fillMaxSize().background(MaterialTheme.colorScheme.secondaryContainer)
)
} else {
Box(modifier = Modifier.fillMaxSize()) {
Text(
modifier =
Modifier.align(Alignment.Center)
.edgeSwipeToDismiss(state)
.horizontalScroll(horizontalScrollState),
text =
"This text can be scrolled horizontally - to dismiss, swipe " +
"right from the left edge of the screen (called Edge Swiping)",
)
}
}
}
}
SimpleSwipeToDismissBox
@Composable
fun SimpleSwipeToDismissBox(navigateBack: () -> Unit) {
SwipeToDismissBox(onDismissed = navigateBack) { isBackground ->
if (isBackground) {
Box(
modifier =
Modifier.fillMaxSize().background(MaterialTheme.colorScheme.secondaryContainer)
)
} else {
Column(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primary),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("Swipe right to dismiss", color = MaterialTheme.colorScheme.onPrimary)
}
}
}
}