Registers a gesture handler.
@Sampled
@Composable
fun OneHandedGestureButtonSample() {
var label by remember { mutableStateOf("Gesturable Button") }
val onClick = { label = "Clicked/Gestured" }
val interactionSource = remember { MutableInteractionSource() }
val gestureConfig =
rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(
onClick = onClick,
interactionSource = interactionSource,
modifier =
Modifier.fillMaxWidth()
.oneHandedGesture(
gestureConfiguration = gestureConfig,
interactionSource = interactionSource,
onGestureLabel = "activate the button",
onGestureAvailable = {
coroutineScope.launch { indicatorState.showIndicator() }
},
onGesture = onClick,
),
) {
OneHandedGestureClickIndicator(gestureConfig, indicatorState) {
Text(label, modifier = Modifier.fillMaxWidth())
}
}
}
}
@Sampled
@Composable
fun OneHandedGestureButtonInAmbientSample() {
var label by remember { mutableStateOf("Gesturable Button") }
val onClick = { label = "Clicked/Gestured" }
val interactionSource = remember { MutableInteractionSource() }
val gestureConfig =
rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val activityAmbientModeManager = rememberAmbientModeManager()
var showGestureIndicator by remember { mutableStateOf(true) }
CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
val isInAmbientMode =
LocalAmbientModeManager.current?.currentAmbientMode is AmbientMode.Ambient
Button(
onClick = onClick,
interactionSource = interactionSource,
modifier =
Modifier.fillMaxWidth()
.oneHandedGesture(
gestureConfiguration = gestureConfig,
interactionSource = interactionSource,
enabledInAmbient = true,
onGestureLabel = "activate the button",
onGestureAvailable = {
// Bypass repeating the gesture indicator on every recomposition.
if (showGestureIndicator) {
coroutineScope.launch { indicatorState.showIndicator() }
showGestureIndicator = false
}
},
onGesture = onClick,
),
colors =
if (isInAmbientMode) ButtonDefaults.outlinedButtonColors()
else ButtonDefaults.buttonColors(),
border =
if (isInAmbientMode) ButtonDefaults.outlinedButtonBorder(enabled = true)
else null,
) {
OneHandedGestureClickIndicator(gestureConfig, indicatorState) {
Text(label, modifier = Modifier.fillMaxWidth())
}
}
}
}
}
@Sampled
@Composable
fun OneHandedGestureTransformingLazyColumnSample() {
val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
val onClick =
remember<() -> Unit> { { backDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() } }
val scrollState = rememberTransformingLazyColumnState()
val coroutineScope = rememberCoroutineScope()
val buttonInteractionSource = remember { MutableInteractionSource() }
val buttonGestureConfig =
rememberOneHandedGestureConfiguration(
action = OneHandedGestureAction.Primary,
priority = OneHandedGesturePriority.Clickable,
)
val buttonIndicatorState = remember { OneHandedGestureClickIndicatorState() }
val scrollGestureConfig =
rememberOneHandedGestureConfiguration(
action = OneHandedGestureAction.Primary,
priority = OneHandedGesturePriority.Scrollable,
)
val scrollIndicatorState =
remember(scrollGestureConfig) { OneHandedGestureScrollIndicatorState() }
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 = {
coroutineScope.launch { buttonIndicatorState.showIndicator() }
},
onGesture = onClick,
)
} then
Modifier.scrollable(
state = scrollState,
orientation = Orientation.Vertical,
reverseDirection = true,
overscrollEffect = rememberOverscrollEffect(),
),
) {
OneHandedGestureClickIndicator(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 = {
coroutineScope.launch { scrollIndicatorState.showIndicator() }
},
onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) },
),
) {
items(10) { Text("Item $it") }
}
}
}
OneHandedGestureHorizontalPagerSample
@Sampled
@Composable
fun OneHandedGestureHorizontalPagerSample() {
val pagerState = rememberPagerState(pageCount = { 10 })
val gestureConfig =
rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary)
val indicatorState = remember { OneHandedGesturePageIndicatorState() }
val coroutineScope = rememberCoroutineScope()
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 = {
coroutineScope.launch { indicatorState.showIndicator() }
},
) {
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")
}
}
}
}
}
}
OneHandedGestureVerticalPagerSample
@Sampled
@Composable
fun OneHandedGestureVerticalPagerSample() {
val pagerState = rememberPagerState(pageCount = { 10 })
val gestureConfig =
rememberOneHandedGestureConfiguration(action = OneHandedGestureAction.Primary)
val indicatorState = remember { OneHandedGesturePageIndicatorState() }
val coroutineScope = rememberCoroutineScope()
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 = {
coroutineScope.launch { indicatorState.showIndicator() }
},
) {
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")
}
}
}
}
}
}