SwipeToReveal

Functions summary

Unit
@ExperimentalWearFoundationApi
@Composable
SwipeToReveal(
    primaryAction: @Composable () -> Unit,
    modifier: Modifier,
    onFullSwipe: () -> Unit,
    state: RevealState,
    secondaryAction: (@Composable () -> Unit)?,
    undoAction: (@Composable () -> Unit)?,
    gestureInclusion: GestureInclusion,
    content: @Composable () -> Unit
)

This function is deprecated. The SwipeToReveal component from the latest material library should be used instead.

Functions

SwipeToReveal

@ExperimentalWearFoundationApi
@Composable
fun SwipeToReveal(
    primaryAction: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    onFullSwipe: () -> Unit = {},
    state: RevealState = rememberRevealState(),
    secondaryAction: (@Composable () -> Unit)? = null,
    undoAction: (@Composable () -> Unit)? = null,
    gestureInclusion: GestureInclusion = SwipeToRevealDefaults.gestureInclusion(state = state),
    content: @Composable () -> Unit
): Unit

A composable that can be used to add extra actions to a composable (up to two) which will be revealed when the original composable is swiped to the left. This composable requires a primary swipe/click action, a secondary optional click action can also be provided.

When the composable reaches the state where all the actions are revealed and the swipe continues beyond the positional threshold defined in RevealState, the primary action is automatically triggered.

An optional undo action can also be added. This undo action will be visible to users once the RevealValue becomes RevealValue.RightRevealed.

It is strongly recommended to have icons represent the actions and maybe a text and icon for the undo action.

Example of SwipeToReveal with primary action and undo action

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.wear.compose.foundation.RevealValue
import androidx.wear.compose.foundation.SwipeToReveal
import androidx.wear.compose.foundation.rememberRevealState
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.Text

val state = rememberRevealState()
val coroutineScope = rememberCoroutineScope()
SwipeToReveal(
    state = state,
    primaryAction = {
        Box(
            modifier =
                Modifier.fillMaxSize().clickable {
                    /* Add the primary action */
                    coroutineScope.launch { state.animateTo(RevealValue.RightRevealed) }
                },
            contentAlignment = Alignment.Center,
        ) {
            Icon(imageVector = Icons.Outlined.Delete, contentDescription = "Delete")
        }
    },
    undoAction = {
        Chip(
            modifier = Modifier.fillMaxWidth(),
            onClick = {
                /* Add the undo action */
                coroutineScope.launch { state.animateTo(RevealValue.Covered) }
            },
            colors = ChipDefaults.secondaryChipColors(),
            label = { Text(text = "Undo") },
        )
    },
) {
    Chip(
        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 */
                            true
                        }
                    )
            },
        onClick = { /* the click action associated with chip */ },
        colors = ChipDefaults.secondaryChipColors(),
        label = { Text(text = "Swipe Me") },
    )
}

Example of SwipeToReveal using RevealState to delay the appearance of primary action text

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.RevealValue
import androidx.wear.compose.foundation.SwipeToReveal
import androidx.wear.compose.foundation.rememberRevealState
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.Text

val state = rememberRevealState()
val coroutineScope = rememberCoroutineScope()
SwipeToReveal(
    state = state,
    primaryAction = {
        Row(
            modifier =
                Modifier.fillMaxSize().clickable {
                    /* Add the primary action */
                    coroutineScope.launch { state.animateTo(RevealValue.RightRevealed) }
                },
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Icon(imageVector = Icons.Outlined.Delete, contentDescription = "Delete")
            if (abs(state.offset) > state.revealThreshold) {
                // Delay the text appearance so that it has enough space to be displayed
                val textAlpha =
                    animateFloatAsState(
                        targetValue = 1f,
                        animationSpec = tween(durationMillis = 250, delayMillis = 250),
                        label = "PrimaryActionTextAlpha",
                    )
                Box(modifier = Modifier.graphicsLayer { alpha = textAlpha.value }) {
                    Spacer(Modifier.size(5.dp))
                    Text("Clear")
                }
            }
        }
    },
    undoAction = {
        Chip(
            modifier = Modifier.fillMaxWidth(),
            onClick = {
                /* Add the undo action */
                coroutineScope.launch { state.animateTo(RevealValue.Covered) }
            },
            colors = ChipDefaults.secondaryChipColors(),
            label = { Text(text = "Undo") },
        )
    },
) {
    Chip(
        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 */
                            true
                        }
                    )
            },
        onClick = { /* the click action associated with chip */ },
        colors = ChipDefaults.secondaryChipColors(),
        label = { Text(text = "Swipe Me") },
    )
}

Example of SwipeToReveal used with Expandables

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.wear.compose.foundation.RevealValue
import androidx.wear.compose.foundation.SwipeToReveal
import androidx.wear.compose.foundation.expandableItem
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.rememberExpandableState
import androidx.wear.compose.foundation.rememberRevealState
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.Text

// Shape of actions should match with the overlay content. For example, Chips
// should use RoundedCornerShape(CornerSize(percent = 50)), Cards should use
// RoundedCornerShape with appropriate radius, based on the theme.
val actionShape = RoundedCornerShape(corner = CornerSize(percent = 50))
val itemCount = 10
val coroutineScope = rememberCoroutineScope()
val expandableStates = List(itemCount) { rememberExpandableState(initiallyExpanded = true) }
ScalingLazyColumn(modifier = Modifier.fillMaxSize()) {
    item { ListHeader { Text("Scaling Lazy Column") } }
    repeat(itemCount) { current ->
        expandableItem(state = expandableStates[current]) { isExpanded ->
            val revealState = rememberRevealState()
            if (isExpanded) {
                SwipeToReveal(
                    state = revealState,
                    primaryAction = {
                        Box(
                            modifier =
                                Modifier.fillMaxSize()
                                    .background(Color.Red, actionShape)
                                    .clickable {
                                        coroutineScope.launch {
                                            revealState.animateTo(RevealValue.RightRevealed)
                                        }
                                    },
                            contentAlignment = Alignment.Center,
                        ) {
                            Icon(
                                imageVector = Icons.Outlined.Delete,
                                contentDescription = "Delete",
                            )
                        }
                    },
                    secondaryAction = {
                        Box(
                            modifier =
                                Modifier.fillMaxSize()
                                    .background(Color.Gray, actionShape)
                                    .clickable { /* trigger the optional action */ },
                            contentAlignment = Alignment.Center,
                        ) {
                            Icon(
                                imageVector = Icons.Outlined.MoreVert,
                                contentDescription = "More Options",
                            )
                        }
                    },
                    undoAction = {
                        Chip(
                            modifier = Modifier.fillMaxWidth(),
                            onClick = {
                                coroutineScope.launch {
                                    revealState.animateTo(RevealValue.Covered)
                                }
                            },
                            colors = ChipDefaults.secondaryChipColors(),
                            label = { Text(text = "Undo") },
                        )
                    },
                    onFullSwipe = {
                        coroutineScope.launch {
                            delay(1000)
                            expandableStates[current].expanded = false
                        }
                    },
                ) {
                    Chip(
                        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 */
                                            coroutineScope.launch {
                                                revealState.animateTo(RevealValue.RightRevealed)
                                            }
                                            true
                                        },
                                        CustomAccessibilityAction("More Options") {
                                            /* Add the secondary action click handler */
                                            true
                                        },
                                    )
                            },
                        onClick = { /* the click action associated with chip */ },
                        colors = ChipDefaults.secondaryChipColors(),
                        label = { Text(text = "Swipe Me") },
                    )
                }
            }
        }
    }
}
Parameters
primaryAction: @Composable () -> Unit

The primary action that will be triggered in the event of a completed swipe. We also strongly recommend to trigger the action when it is clicked.

modifier: Modifier = Modifier

Optional Modifier for this component.

onFullSwipe: () -> Unit = {}

An optional lambda which will be triggered when a full swipe from either of the anchors is performed.

state: RevealState = rememberRevealState()

The RevealState of this component. It can be used to customise the anchors and threshold config of the swipeable modifier which is applied.

secondaryAction: (@Composable () -> Unit)? = null

An optional action that can be added to the component. We strongly recommend triggering the action when it is clicked.

undoAction: (@Composable () -> Unit)? = null

The optional undo action that will be applied to the component once the the RevealState.currentValue becomes RevealValue.RightRevealed.

gestureInclusion: GestureInclusion = SwipeToRevealDefaults.gestureInclusion(state = state)

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 except a zone on the left edge, which is used for swipe-to-dismiss (see SwipeToRevealDefaults.gestureInclusion).

content: @Composable () -> Unit

The content that will be initially displayed over the other actions provided. Custom accessibility actions should always be added to the content using androidx.compose.ui.semantics.semantics - examples are shown in the code samples.