swipeable

Compose Modifier

Common

Deprecated SwipeableDeprecation

@ExperimentalMaterialApi
fun <T> Modifier.swipeable(
    state: SwipeableState<T>,
    anchors: Map<Float, T>,
    orientation: Orientation,
    enabled: Boolean = true,
    reverseDirection: Boolean = false,
    interactionSource: MutableInteractionSource? = null,
    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
    velocityThreshold: Dp = VelocityThreshold,
) =
    composed(
        inspectorInfo =
            debugInspectorInfo {
                name = "swipeable"
                properties["state"] = state
                properties["anchors"] = anchors
                properties["orientation"] = orientation
                properties["enabled"] = enabled
                properties["reverseDirection"] = reverseDirection
                properties["interactionSource"] = interactionSource
                properties["thresholds"] = thresholds
                properties["resistance"] = resistance
                properties["velocityThreshold"] = velocityThreshold
            }
    ) {
        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
        require(anchors.values.distinct().count() == anchors.size) {
            "You cannot have two anchors mapped to the same state."
        }
        val density = LocalDensity.current
        state.ensureInit(anchors)
        LaunchedEffect(anchors, state) {
            val oldAnchors = state.anchors
            state.anchors = anchors
            state.resistance = resistance
            state.thresholds = { a, b ->
                val from = anchors.getValue(a)
                val to = anchors.getValue(b)
                with(thresholds(from, to)) { density.computeThreshold(a, b) }
            }
            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
            state.processNewAnchors(oldAnchors, anchors)
        }

        Modifier.draggable(
            orientation = orientation,
            enabled = enabled,
            reverseDirection = reverseDirection,
            interactionSource = interactionSource,
            startDragImmediately = state.isAnimationRunning,
            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
            state = state.draggableState,
        )
    }

Enable swipe gestures between a set of predefined states.

To use this, you must provide a map of anchors (in pixels) to states (of type T). Note that this map cannot be empty and cannot have two anchors mapped to the same state.

When a swipe is detected, the offset of the SwipeableState will be updated with the swipe delta. You should use this offset to move your content accordingly (see Modifier.offsetPx). When the swipe ends, the offset will be animated to one of the anchors and when that anchor is reached, the value of the SwipeableState will also be updated to the state corresponding to the new anchor. The target anchor is calculated based on the provided positional thresholds.

Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe past these bounds, a resistance effect will be applied by default. The amount of resistance at each edge is specified by the resistance config. To disable all resistance, set it to null.

For an example of a swipeable with three states, see:

Parameters

TThe type of the state.
stateThe state of the swipeable.
anchorsPairs of anchors and states, used to map anchors to states and vice versa.
thresholdsSpecifies where the thresholds between the states are. The thresholds will be used to determine which state to animate to when swiping stops. This is represented as a lambda that takes two states and returns the threshold between them in the form of a ThresholdConfig. Note that the order of the states corresponds to the swipe direction.
orientationThe orientation in which the swipeable can be swiped.
enabledWhether this swipeable is enabled and should react to the user's input.
reverseDirectionWhether to reverse the direction of the swipe, so a top to bottom swipe will behave like bottom to top, and a left to right swipe will behave like right to left.
interactionSourceOptional MutableInteractionSource that will passed on to the internal Modifier.draggable.
resistanceControls how much resistance will be applied when swiping past the bounds.
velocityThresholdThe threshold (in dp per second) that the end velocity has to exceed in order to animate to the next state, even if the positional thresholds have not been reached.

Code Examples

SwipeableSample

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun SwipeableSample() {
    // Draw a slider-like composable consisting of a red square moving along a
    // black background, with three states: "A" (min), "B" (middle), and "C" (max).
    val width = 350.dp
    val squareSize = 50.dp
    val swipeableState = rememberSwipeableState("A")
    val sizePx = with(LocalDensity.current) { (width - squareSize).toPx() }
    val anchors = mapOf(0f to "A", sizePx / 2 to "B", sizePx to "C")
    Box(
        modifier =
            Modifier.width(width)
                .swipeable(
                    state = swipeableState,
                    anchors = anchors,
                    thresholds = { _, _ -> FractionalThreshold(0.5f) },
                    orientation = Orientation.Horizontal,
                )
                .background(Color.Black)
    ) {
        Box(
            Modifier.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.Red),
            contentAlignment = Alignment.Center,
        ) {
            Text(swipeableState.currentValue, color = Color.White, fontSize = 24.sp)
        }
    }
}