SwipeToReveal

Composable Component

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.

Android
@Composable
public 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

primaryActionThe 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.
onSwipePrimaryActionA callback which will be triggered when a full swipe is performed. It is expected that the same callback is given to SwipeToRevealScope.PrimaryActionButtons onClick action. If undoPrimaryAction is provided, that will be displayed after the swipe gesture is completed.
modifierModifier to be applied on the composable.
secondaryActionOptional 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.
undoPrimaryActionOptional 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.
undoSecondaryActionOptional 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.
revealStateRevealState of the SwipeToReveal.
revealDirectionThe 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.
hasPartiallyRevealedStateDetermines 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).
gestureInclusionProvides 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).
contentThe 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

SwipeToRevealNoPartialRevealWithScalingLazyColumnSample

@Preview
@Composable
fun SwipeToRevealNoPartialRevealWithScalingLazyColumnSample() {
    val slcState = rememberScalingLazyListState()
    ScalingLazyColumn(
        state = slcState,
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp),
        modifier = Modifier.background(Color.Black),
    ) {
        items(count = 100) { index ->
            SwipeToReveal(
                hasPartiallyRevealedState = false,
                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") },
                    )
                },
            ) {
                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())
                }
            }
        }
    }
}

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(),
            )
        }
    }
}

SwipeToRevealWithScalingLazyColumnSample

@Preview
@Composable
fun SwipeToRevealWithScalingLazyColumnSample() {
    val slcState = rememberScalingLazyListState()
    val coroutineScope = rememberCoroutineScope()
    ScalingLazyColumn(
        state = slcState,
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp),
        modifier = Modifier.background(Color.Black),
    ) {
        items(count = 100) { index ->
            val revealState = rememberRevealState()
            // SwipeToReveal should be reset to covered when scrolling occurs.
            LaunchedEffect(slcState.isScrollInProgress) {
                if (
                    slcState.isScrollInProgress && revealState.currentValue != RevealValue.Covered
                ) {
                    coroutineScope.launch {
                        revealState.animateTo(targetValue = RevealValue.Covered)
                    }
                }
            }
            SwipeToReveal(
                revealState = revealState,
                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())
                }
            }
        }
    }
}

SwipeToRevealWithTransformingLazyColumnSample

@Preview
@Composable
fun SwipeToRevealWithTransformingLazyColumnSample() {
    val transformationSpec = rememberTransformationSpec()
    val tlcState = rememberTransformingLazyColumnState()
    val coroutineScope = rememberCoroutineScope()
    TransformingLazyColumn(
        state = tlcState,
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 20.dp),
        modifier = Modifier.background(Color.Black),
    ) {
        items(count = 100) { index ->
            val revealState = rememberRevealState()
            // SwipeToReveal should be reset to covered when scrolling occurs.
            LaunchedEffect(tlcState.isScrollInProgress) {
                if (
                    tlcState.isScrollInProgress && revealState.currentValue != RevealValue.Covered
                ) {
                    coroutineScope.launch {
                        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") },
                        modifier = Modifier.height(SwipeToRevealDefaults.LargeActionButtonHeight),
                    )
                },
                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("Subtitle") },
                    modifier =
                        Modifier.semantics {
                            // Use custom actions to make the primary action accessible
                            customActions =
                                listOf(
                                    CustomAccessibilityAction("Delete") {
                                        /* Add the primary action click handler here */
                                        true
                                    }
                                )
                        },
                ) {
                    Text("Message body which extends over multiple lines to extend the card")
                }
            }
        }
    }
}

Create your own Component Library

Material Components are meant to be used as is and they do not allow customizations. To build your own Jetpack Compose component library use Compose Unstyled