oneHandedGesture

Functions summary

Modifier
@Composable
Modifier.oneHandedGesture(
    action: GestureAction,
    key: String?,
    priority: GesturePriority,
    enabledInAmbient: Boolean,
    interactionSource: MutableInteractionSource?,
    onShowIndicator: () -> Unit,
    onGesture: suspend () -> Unit
)

Registers a gesture handler.

Functions

Modifier.oneHandedGesture

@Composable
fun Modifier.oneHandedGesture(
    action: GestureAction,
    key: String? = null,
    priority: GesturePriority = GesturePriority.Unspecified,
    enabledInAmbient: Boolean = false,
    interactionSource: MutableInteractionSource? = null,
    onShowIndicator: () -> Unit = {},
    onGesture: suspend () -> Unit
): Modifier

Registers a gesture handler.

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.OneHandedGestureDefaults
import androidx.wear.compose.material3.onehandedgesture.oneHandedGesture

var label by remember { mutableStateOf("Gesturable Button") }
val onClick = remember { { label = "Clicked/Gestured" } }
var gestureIndicatorVisible by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
    Button(
        onClick = onClick,
        interactionSource = interactionSource,
        modifier =
            Modifier.oneHandedGesture(
                action = GestureAction.Primary,
                interactionSource = interactionSource,
                onShowIndicator = { gestureIndicatorVisible = true },
                onGesture = onClick,
            ),
    ) {
        OneHandedGestureDefaults.GestureIndicator(
            gestureIndicatorVisible,
            { gestureIndicatorVisible = false },
        ) {
            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.mutableStateOf
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.oneHandedGesture

val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
val onClick =
    remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } }
val tlcState = rememberTransformingLazyColumnState()
var scrollGestureIndicatorVisible by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }

ScreenScaffold(
    scrollState = tlcState,
    edgeButton = {
        var buttonGestureIndicatorVisible by remember { mutableStateOf(false) }
        EdgeButton(
            onClick = onClick,
            interactionSource = interactionSource,
            modifier =
                if (tlcState.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(
                        action = GestureAction.Primary,
                        priority = GesturePriority.Clickable,
                        interactionSource = interactionSource,
                        onShowIndicator = { buttonGestureIndicatorVisible = true },
                        onGesture = onClick,
                    )
                } then
                    Modifier.scrollable(
                        tlcState,
                        orientation = Orientation.Vertical,
                        reverseDirection = true,
                        overscrollEffect = rememberOverscrollEffect(),
                    ),
        ) {
            OneHandedGestureDefaults.GestureIndicator(
                buttonGestureIndicatorVisible,
                { buttonGestureIndicatorVisible = false },
            ) {
                Text("Close")
            }
        }
    },
    scrollIndicator = {
        OneHandedGestureDefaults.ScrollGestureIndicator(
            scrollGestureIndicatorVisible,
            onGestureIndicatorFinished = { scrollGestureIndicatorVisible = false },
            tlcState,
            modifier = Modifier.align(Alignment.CenterEnd),
        )
    },
) { contentPadding ->
    TransformingLazyColumn(
        state = tlcState,
        contentPadding = contentPadding,
        modifier =
            Modifier.fillMaxSize()
                .oneHandedGesture(
                    action = GestureAction.Primary,
                    priority = GesturePriority.Scrollable,
                    onGesture = { OneHandedGestureDefaults.scrollDown(tlcState) },
                    onShowIndicator = { scrollGestureIndicatorVisible = true },
                ),
    ) {
        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.mutableStateOf
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.oneHandedGesture

val pagerState = rememberPagerState(pageCount = { 10 })
var pageGestureIndicatorVisible by remember { mutableStateOf(false) }

HorizontalPagerScaffold(
    pagerState = pagerState,
    pageIndicator = {
        OneHandedGestureDefaults.HorizontalPageGestureIndicator(
            pagerState = pagerState,
            gestureIndicatorVisible = pageGestureIndicatorVisible,
            onGestureIndicatorFinished = { pageGestureIndicatorVisible = false },
        )
    },
) {
    HorizontalPager(
        state = pagerState,
        modifier =
            Modifier.oneHandedGesture(
                action = GestureAction.Primary,
                onShowIndicator = { pageGestureIndicatorVisible = 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.mutableStateOf
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.oneHandedGesture

val pagerState = rememberPagerState(pageCount = { 10 })
var pageGestureIndicatorVisible by remember { mutableStateOf(false) }

VerticalPagerScaffold(
    pagerState = pagerState,
    pageIndicator = {
        OneHandedGestureDefaults.VerticalPageGestureIndicator(
            pagerState = pagerState,
            gestureIndicatorVisible = pageGestureIndicatorVisible,
            onGestureIndicatorFinished = { pageGestureIndicatorVisible = false },
        )
    },
) {
    VerticalPager(
        state = pagerState,
        modifier =
            Modifier.oneHandedGesture(
                action = GestureAction.Primary,
                onShowIndicator = { pageGestureIndicatorVisible = 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
action: GestureAction

The gesture action to handle.

key: String? = null

A unique identifier for this gesture instance. This ID allows the system to track user interactions - for example, to mute gesture indicators that have been frequently shown or successfully performed, in accordance with user preferences. If the same key is reused across multiple gestures, they will share a common interaction history (such as frequency-based gesture indicator display logic). Note that this only affects the presentation of the UI; the underlying logic and handling remain independent for each instance. If key is null or empty, a unique key will be automatically generated based on the composition position of this gesture.

priority: GesturePriority = GesturePriority.Unspecified

The priority value; higher values take precedence if multiple handlers are registered for the same action. It is not recommended to register multiple gestures for the same action and priority (but if that is the case, all of them will be actioned).

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.Interactions for this gesture. This can be used to visualize the gesture state (e.g., showing a ripple or custom pressed state) when the one-handed gesture is being interacted with.

onShowIndicator: () -> Unit = {}

Callback invoked when the system determines a gesture indicator should be displayed for this component. This occurs when the component is visible and holds the highest priority for the current gesture. Only GestureAction.Primary gesture indicator callbacks will be called.

onGesture: suspend () -> Unit

The callback invoked when the gesture is triggered.