SwipeToReveal
Component in Wear Material 3 Compose
[SwipeToReveal] Material composable. This adds the option to configure up to two additional actions on a Composable: a mandatory [primaryAction] and an optional [secondaryAction]. These actions are initially hidden (unless [RevealState] is created with an initial value other than [RevealValue.Covered]) and revealed only when the [content] is swiped - the action buttons can then be clicked. A full swipe of the [content] triggers the [onSwipePrimaryAction] callback, which is expected to match the [primaryAction]'s onClick callback. Custom accessibility actions should always be added to the content using [Modifier.semantics] - examples are shown in the code samples.
Adding undo actions allows users to undo a primary or secondary action that may have been performed inadvertently. The corresponding undo action is displayed when the primary or secondary action is triggered via click or swipe and after [SwipeToReveal] has animated to the revealed state. After the undo action is clicked, [SwipeToReveal] animates back to the [RevealValue.Covered] state and the user can then swipe to reveal if they wish to perform another action.
For destructive actions like "Delete", consider making this the primary action, and providing the [undoPrimaryAction].
When using SwipeToReveal with large content items like [Card]s, it is recommended to set the height of [SwipeToRevealScope.PrimaryActionButton] and [SwipeToRevealScope.SecondaryActionButton] to [SwipeToRevealDefaults.LargeActionButtonHeight] using [Modifier.height] - in other cases, the [Button] displayed by [SwipeToReveal] has height [ButtonDefaults.Height] by default. It is recommended to always use the default undo button height as created by [SwipeToRevealScope.UndoActionButton].
If [revealDirection] is set to [RevealDirection.Bidirectional], the actions revealed on swipe are the same on both sides.
Last updated:
Installation
dependencies {
implementation("androidx.wear.compose:compose-material3:1.5.0-beta01")
}
Overloads
@Composable
fun SwipeToReveal(
primaryAction: @Composable SwipeToRevealScope.() -> Unit,
onSwipePrimaryAction: () -> Unit,
modifier: Modifier = Modifier,
secondaryAction: (@Composable SwipeToRevealScope.() -> Unit)? = null,
undoPrimaryAction: (@Composable SwipeToRevealScope.() -> Unit)? = null,
undoSecondaryAction: (@Composable SwipeToRevealScope.() -> Unit)? = null,
revealState: RevealState = rememberRevealState(),
revealDirection: RevealDirection = RevealDirection.RightToLeft,
hasPartiallyRevealedState: Boolean = true,
gestureInclusion: GestureInclusion =
if (revealDirection == Bidirectional) {
bidirectionalGestureInclusion
} else {
gestureInclusion(revealState)
},
content: @Composable () -> Unit,
)
Parameters
name | description |
---|---|
primaryAction | The primary action of this component. [SwipeToRevealScope.PrimaryActionButton] should be used to create a button for this slot. If [undoPrimaryAction] is provided, the undo button will be displayed after [SwipeToReveal] has animated to the revealed state and the primary action button has been hidden. |
onSwipePrimaryAction | A callback which will be triggered when a full swipe is performed. It is expected that the same callback is given to [SwipeToRevealScope.PrimaryActionButton]s onClick action. If [undoPrimaryAction] is provided, that will be displayed after the swipe gesture is completed. |
modifier | [Modifier] to be applied on the composable. |
secondaryAction | Optional secondary action of this component. [SwipeToRevealScope.SecondaryActionButton] should be used to create a button for this slot. If [undoSecondaryAction] is provided, the undo button will be displayed after [SwipeToReveal] has animated to the revealed state and the secondary action button has been hidden. |
undoPrimaryAction | Optional undo action for the primary action of this component. [SwipeToRevealScope.UndoActionButton] should be used to create a button for this slot. Displayed after [SwipeToReveal] has animated to the revealed state and the primary action button has been hidden. |
undoSecondaryAction | Optional undo action for the secondary action of this component, displayed after [SwipeToReveal] has animated to the revealed state and the secondary action button has been hidden. [undoSecondaryAction] is ignored if the secondary action has not been specified. [SwipeToRevealScope.UndoActionButton] should be used to create a button for this slot. |
revealState | [RevealState] of the [SwipeToReveal]. |
revealDirection | The direction from which [SwipeToReveal] can reveal the actions. It is strongly recommended to respect the default value of [RightToLeft] to avoid conflicting with the system-side swipe-to-dismiss gesture. |
hasPartiallyRevealedState | Determines whether the intermediate states [RightRevealing] and [LeftRevealing] are used. These indicate a settled state, where the primary action is partially revealed. By default, partially revealed state is allowed for single actions - set to false to make actions complete when swiped instead. This flag has no effect if a secondary action is provided (when there are two actions, the component always allows the partially revealed states). |
gestureInclusion | Provides fine-grained control so that touch gestures can be excluded when they start in a certain region. An instance of [GestureInclusion] can be passed in here which will determine via [GestureInclusion.ignoreGestureStart] whether the gesture should proceed or not. By default, [gestureInclusion] allows gestures everywhere for when [revealState] contains anchors for both directions (see [bidirectionalGestureInclusion]). If it doesn't, then it allows gestures everywhere, except a zone on the left edge, which is used for swipe-to-dismiss (see [gestureInclusion]). |
content | The content that will be initially displayed over the other actions provided. Custom accessibility actions should always be added to the content using [Modifier.semantics] - examples are shown in the code samples. |
Code Examples
SwipeToRevealSample
@Composable
fun SwipeToRevealSample() {
SwipeToReveal(
primaryAction = {
PrimaryActionButton(
onClick = { /* This block is called when the primary action is executed. */ },
icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
text = { Text("Delete") }
)
},
onSwipePrimaryAction = { /* This block is called when the full swipe gesture is performed. */
},
secondaryAction = {
SecondaryActionButton(
onClick = { /* This block is called when the secondary action is executed. */ },
icon = { Icon(Icons.Outlined.MoreVert, contentDescription = "Options") }
)
},
undoPrimaryAction = {
UndoActionButton(
onClick = { /* This block is called when the undo primary action is executed. */ },
text = { Text("Undo Delete") },
)
},
) {
Button(
modifier =
Modifier.fillMaxWidth().semantics {
// Use custom actions to make the primary and secondary actions accessible
customActions =
listOf(
CustomAccessibilityAction("Delete") {
/* Add the primary action click handler here */
true
},
CustomAccessibilityAction("Options") {
/* Add the secondary click handler here */
true
}
)
},
onClick = {}
) {
Text("This Button has two actions", modifier = Modifier.fillMaxSize())
}
}
}
SwipeToRevealSingleActionCardSample
@Composable
fun SwipeToRevealSingleActionCardSample() {
SwipeToReveal(
primaryAction = {
PrimaryActionButton(
onClick = { /* This block is called when the primary action is executed. */ },
icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
text = { Text("Delete") },
modifier = Modifier.height(SwipeToRevealDefaults.LargeActionButtonHeight)
)
},
onSwipePrimaryAction = { /* This block is called when the full swipe gesture is performed. */
},
undoPrimaryAction = {
UndoActionButton(
onClick = { /* This block is called when the undo primary action is executed. */ },
text = { Text("Undo Delete") },
)
},
) {
Card(
modifier =
Modifier.fillMaxWidth().semantics {
// Use custom actions to make the primary action accessible
customActions =
listOf(
CustomAccessibilityAction("Delete") {
/* Add the primary action click handler here */
true
},
)
},
onClick = {}
) {
Text(
"This Card has one action, and the revealed button is taller",
modifier = Modifier.fillMaxSize()
)
}
}
}
SwipeToRevealNoPartiallyRevealedStateSample
@Composable
fun SwipeToRevealNoPartiallyRevealedStateSample() {
SwipeToReveal(
primaryAction = {
PrimaryActionButton(
onClick = { /* This block is called when the primary action is executed. */ },
icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
text = { Text("Delete") }
)
},
onSwipePrimaryAction = { /* This block is called when the full swipe gesture is performed. */
},
undoPrimaryAction = {
UndoActionButton(
onClick = { /* This block is called when the undo primary action is executed. */ },
icon = { Icon(Icons.Outlined.Refresh, contentDescription = "Undo") },
text = { Text("Undo") },
)
},
hasPartiallyRevealedState = false
) {
Button(
modifier =
Modifier.fillMaxWidth().semantics {
// Use custom actions to make the primary action accessible
customActions =
listOf(
CustomAccessibilityAction("Delete") {
/* Add the primary action click handler here */
true
},
)
},
onClick = {}
) {
Text("Swipe to execute the primary action.", modifier = Modifier.fillMaxSize())
}
}
}
SwipeToRevealWithTransformingLazyColumnSample
@Preview
@Composable
fun SwipeToRevealWithTransformingLazyColumnSample() {
val transformationSpec = rememberTransformationSpec()
val tlcState = rememberTransformingLazyColumnState()
TransformingLazyColumn(
state = tlcState,
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp),
modifier = Modifier.background(Color.Black)
) {
items(count = 100) { index ->
val revealState = rememberRevealState()
// SwipeToReveal is covered on scroll.
LaunchedEffect(tlcState.isScrollInProgress) {
if (
tlcState.isScrollInProgress && revealState.currentValue != RevealValue.Covered
) {
revealState.animateTo(targetValue = RevealValue.Covered)
}
}
SwipeToReveal(
primaryAction = {
PrimaryActionButton(
onClick = { /* Called when the primary action is executed. */ },
icon = { Icon(Icons.Outlined.Delete, contentDescription = "Delete") },
text = { Text("Delete") }
)
},
revealState = revealState,
onSwipePrimaryAction = { /* This block is called when the full swipe gesture is performed. */
},
modifier =
Modifier.transformedHeight(this@items, transformationSpec).graphicsLayer {
with(transformationSpec) { applyContainerTransformation(scrollProgress) }
// Is needed to disable clipping.
compositingStrategy = CompositingStrategy.ModulateAlpha
clip = false
},
) {
TitleCard(
onClick = {},
title = { Text("Message #$index") },
subtitle = { Text("Body of the message") },
modifier =
Modifier.semantics {
// Use custom actions to make the primary action accessible
customActions =
listOf(
CustomAccessibilityAction("Delete") {
/* Add the primary action click handler here */
true
},
)
}
)
}
}
}
}