oneHandedGesture

Functions summary

Modifier
Modifier.oneHandedGesture(
    gestureConfiguration: OneHandedGestureConfiguration,
    onGestureLabel: String?,
    enabledInAmbient: Boolean,
    interactionSource: MutableInteractionSource?,
    onGestureAvailable: () -> Unit,
    onGesture: suspend () -> Unit
)

Registers a gesture handler.

Functions

Modifier.oneHandedGesture

fun Modifier.oneHandedGesture(
    gestureConfiguration: OneHandedGestureConfiguration,
    onGestureLabel: String?,
    enabledInAmbient: Boolean = false,
    interactionSource: MutableInteractionSource? = null,
    onGestureAvailable: () -> Unit = {},
    onGesture: suspend () -> Unit
): Modifier

Registers a gesture handler.

Note: Gesture recognition can be explicitly disabled across a component hierarchy by providing false` to LocalOneHandedGestureEnabled.

Visibility Management: This gesture handler is active as long as the Modifier is part of the composition. On its own, it does not track whether the composable is visible or clipped (e.g., in a Lazy layout).

To prevent accidental triggers from off-screen items, developers should apply this modifier conditionally. For many cases, androidx.compose.ui.layout.onVisibilityChanged Modifier can be used to determine the visibility of a composable.

Example usage in a list:

var isVisible by remember { mutableStateOf(false) }
val gestureModifier = remember(isVisible) {
if (isVisible) Modifier.oneHandedGesture() else Modifier
}

Box(
modifier = Modifier
.onVisibilityChanged { isVisible = it }
.then(gestureModifier)
)
{
...
}

Haptics: When a gesture is successfully triggered, the system automatically performs haptic feedback to acknowledge the interaction; developers do not need to trigger haptics manually within onGesture.

Example of adding one-handed gesture handler to a androidx.wear.compose.material3.Button:

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.onehandedgesture.GestureAction
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicator
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicatorState
import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture
import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration

var label by remember { mutableStateOf("Gesturable Button") }
val onClick = { label = "Clicked/Gestured" }
val interactionSource = remember { MutableInteractionSource() }
val gestureConfig = rememberOneHandedGestureConfiguration(action = GestureAction.Primary)
val indicatorState = remember { OneHandedGestureIndicatorState() }

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
    Button(
        onClick = onClick,
        interactionSource = interactionSource,
        modifier =
            Modifier.oneHandedGesture(
                gestureConfiguration = gestureConfig,
                interactionSource = interactionSource,
                onGestureLabel = "activate the button",
                onGestureAvailable = { indicatorState.isIndicatorActive = true },
                onGesture = onClick,
            ),
    ) {
        OneHandedGestureIndicator(gestureConfig, indicatorState) { Text(label) }
    }
}

Example of adding one-handed gesture handler to a androidx.wear.compose.foundation.lazy.TransformingLazyColumn:

import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.onehandedgesture.GestureAction
import androidx.wear.compose.material3.onehandedgesture.GesturePriority
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureDefaults
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicator
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicatorState
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureScrollIndicator
import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture
import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration

val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
val onClick =
    remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } }
val scrollState = rememberTransformingLazyColumnState()
val buttonInteractionSource = remember { MutableInteractionSource() }
val buttonGestureConfig =
    rememberOneHandedGestureConfiguration(
        action = GestureAction.Primary,
        priority = GesturePriority.Clickable,
    )
val buttonIndicatorState = remember { OneHandedGestureIndicatorState() }

val scrollGestureConfig =
    rememberOneHandedGestureConfiguration(
        action = GestureAction.Primary,
        priority = GesturePriority.Scrollable,
    )
val scrollIndicatorState = remember { OneHandedGestureIndicatorState() }
ScreenScaffold(
    scrollState = scrollState,
    edgeButton = {
        EdgeButton(
            onClick = onClick,
            interactionSource = buttonInteractionSource,
            modifier =
                if (scrollState.canScrollForward) {
                    Modifier
                } else {
                    // Apply the one-handed gesture modifier only when the container cannot
                    // scroll further, ensuring the EdgeButton is fully visible and interactive
                    Modifier.oneHandedGesture(
                        gestureConfiguration = buttonGestureConfig,
                        interactionSource = buttonInteractionSource,
                        onGestureLabel = "close",
                        onGestureAvailable = { buttonIndicatorState.isIndicatorActive = true },
                        onGesture = onClick,
                    )
                } then
                    Modifier.scrollable(
                        state = scrollState,
                        orientation = Orientation.Vertical,
                        reverseDirection = true,
                        overscrollEffect = rememberOverscrollEffect(),
                    ),
        ) {
            OneHandedGestureIndicator(buttonGestureConfig, buttonIndicatorState) {
                Text("Close")
            }
        }
    },
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = scrollGestureConfig,
            indicatorState = scrollIndicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd),
        )
    },
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier =
            Modifier.fillMaxSize()
                .oneHandedGesture(
                    gestureConfiguration = scrollGestureConfig,
                    onGestureLabel = "scroll",
                    onGestureAvailable = { scrollIndicatorState.isIndicatorActive = true },
                    onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) },
                ),
    ) {
        items(10) { Text("Item $it") }
    }
}

Example of adding one-handed gesture handler to a androidx.wear.compose.foundation.pager.HorizontalPager:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.pager.HorizontalPager
import androidx.wear.compose.foundation.pager.rememberPagerState
import androidx.wear.compose.material3.AnimatedPage
import androidx.wear.compose.material3.HorizontalPagerScaffold
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.onehandedgesture.GestureAction
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureDefaults
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureHorizontalPageIndicator
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicator
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicatorState
import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture
import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration

val pagerState = rememberPagerState(pageCount = { 10 })
val gestureConfig = rememberOneHandedGestureConfiguration(action = GestureAction.Primary)
val indicatorState = remember { OneHandedGestureIndicatorState() }

HorizontalPagerScaffold(
    pagerState = pagerState,
    pageIndicator = {
        OneHandedGestureHorizontalPageIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            pagerState = pagerState,
        )
    },
) {
    HorizontalPager(
        state = pagerState,
        modifier =
            Modifier.oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll to the next page",
                onGestureAvailable = { indicatorState.isIndicatorActive = true },
            ) {
                OneHandedGestureDefaults.scrollToNextPage(pagerState)
            },
    ) { page ->
        AnimatedPage(pageIndex = page, pagerState = pagerState) {
            ScreenScaffold {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center,
                ) {
                    Text(text = "Page #$page")
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(text = "Swipe left and right")
                }
            }
        }
    }
}

Example of adding one-handed gesture handler to a androidx.wear.compose.foundation.pager.VerticalPager:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.pager.VerticalPager
import androidx.wear.compose.foundation.pager.rememberPagerState
import androidx.wear.compose.material3.AnimatedPage
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.VerticalPagerScaffold
import androidx.wear.compose.material3.onehandedgesture.GestureAction
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureDefaults
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicator
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureIndicatorState
import androidx.wear.compose.material3.onehandedgesture.OneHandedGestureVerticalPageIndicator
import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture
import androidx.wear.compose.material3.onehandedgesture.rememberOneHandedGestureConfiguration

val pagerState = rememberPagerState(pageCount = { 10 })
val gestureConfig = rememberOneHandedGestureConfiguration(action = GestureAction.Primary)
val indicatorState = remember { OneHandedGestureIndicatorState() }

VerticalPagerScaffold(
    pagerState = pagerState,
    pageIndicator = {
        OneHandedGestureVerticalPageIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            pagerState = pagerState,
        )
    },
) {
    VerticalPager(
        state = pagerState,
        modifier =
            Modifier.oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll to the next page",
                onGestureAvailable = { indicatorState.isIndicatorActive = true },
            ) {
                OneHandedGestureDefaults.scrollToNextPage(pagerState)
            },
    ) { page ->
        AnimatedPage(pageIndex = page, pagerState = pagerState) {
            ScreenScaffold {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center,
                ) {
                    Text(text = "Page #$page")
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(text = "Swipe up and down")
                }
            }
        }
    }
}
Parameters
gestureConfiguration: OneHandedGestureConfiguration

The OneHandedGestureConfiguration containing the configuration for this gesture.

onGestureLabel: String?

Semantic label used by accessibility services to describe the purpose of this gesture. This is highly recommended for ensuring that users with screen readers understand what action will be performed.

enabledInAmbient: Boolean = false

Whether the gesture should remain active in ambient mode.

interactionSource: MutableInteractionSource? = null

MutableInteractionSource that will be used to dispatch androidx.compose.foundation.interaction.PressInteraction.Press and an immediate androidx.compose.foundation.interaction.PressInteraction.Release when this gesture is triggered. This allows UI components to visually react (such as triggering a touch ripple or pressed state animation) upon gesture activation.

onGestureAvailable: () -> Unit = {}

A callback invoked by the system to signal that this gesture is currently available as a high-priority action. Developers should use this callback to set OneHandedGestureIndicatorState.isIndicatorActive to true to trigger the associated visual feedback.

onGesture: suspend () -> Unit

The callback invoked when the gesture is triggered.