EnableInsetFocusRingSample
@Sampled
@Composable
fun EnableInsetFocusRingSample() {
// Enable inset focus rings through the theme configuration.
CompositionLocalProvider(
LocalRippleThemeConfiguration provides RippleDefaults.InsetFocusRingThemeConfiguration
) {
// Button is a standard Wear Material3 component that supports focus rings internally.
Button(onClick = { /* Do something */ }, label = { Text("Button") })
}
}
CustomizeInsetFocusRingSample
@Sampled
@Composable
fun CustomizeInsetFocusRingSample() {
// Customize the focus ring stroke sizes through the theme configuration.
CompositionLocalProvider(
LocalRippleThemeConfiguration provides
RippleThemeConfiguration(
RippleThemeConfiguration.Focus.InsetRing(
outerStrokeInset = 0.dp,
outerStrokeWidth = 3.dp,
innerStrokeInset = 0.dp,
innerStrokeWidth = 6.dp,
)
)
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
// Button is a standard Wear Material3 component that supports focus rings internally.
Button(onClick = { /* Do something */ }, label = { Text("Button") })
// SwitchButton is also a standard component that supports focus rings internally.
// We override its focus ring colors here.
CompositionLocalProvider(
LocalRippleConfiguration provides
RippleConfiguration(
focus =
RippleConfiguration.Focus.InsetRing(
outerStrokeColor = MaterialTheme.colorScheme.tertiary,
innerStrokeColor = MaterialTheme.colorScheme.onTertiary,
)
)
) {
SwitchButton(
checked = true,
onCheckedChange = { /* Do something */ },
label = { Text("SwitchButton") },
)
}
}
}
}
ClickableWithFocusRingShapeSample
@Sampled
@Composable
fun ClickableWithFocusRingShapeSample() {
CompositionLocalProvider(
LocalRippleThemeConfiguration provides RippleDefaults.InsetFocusRingThemeConfiguration
) {
// Pass ripple() explicitly to use the same shape for the component and its focus ring.
val shape = MaterialTheme.shapes.large
Box(
modifier =
Modifier.fillMaxWidth()
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceContainer)
.clickable(
interactionSource = null,
indication = ripple(focusRingShape = shape),
onClick = { /* Do something */ },
)
.padding(12.dp),
contentAlignment = Alignment.Center,
) {
Text("Clickable box")
}
}
}
FocusableWithFocusRingShapeSample
@Sampled
@Composable
fun FocusableWithFocusRingShapeSample() {
CompositionLocalProvider(
LocalRippleThemeConfiguration provides RippleDefaults.InsetFocusRingThemeConfiguration
) {
val shape = MaterialTheme.shapes.large
val interactionSource = remember { MutableInteractionSource() }
// indication draws but does not emit interactions; focusable (or clickable) emits them.
Box(
modifier =
Modifier.fillMaxWidth()
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceContainer)
.indication(interactionSource, ripple(focusRingShape = shape))
.focusable(interactionSource = interactionSource)
.padding(12.dp),
contentAlignment = Alignment.Center,
) {
Text("Focusable box")
}
}
}
BorderedClickableWithFocusRingShapeSample
@Sampled
@Composable
fun BorderedClickableWithFocusRingShapeSample() {
CompositionLocalProvider(
LocalRippleThemeConfiguration provides RippleDefaults.InsetFocusRingThemeConfiguration
) {
val shape = MaterialTheme.shapes.large
val hasInsetFocusRing =
LocalRippleThemeConfiguration.current.focus is RippleThemeConfiguration.Focus.InsetRing
val interactionSource =
if (hasInsetFocusRing) remember { MutableInteractionSource() } else null
Box(
modifier =
Modifier.fillMaxWidth()
// Draw the focus ring before the border, so that it is drawn on top of it.
.then(
if (interactionSource != null) {
Modifier.indication(
interactionSource = interactionSource,
indication =
ripple(
focusRingShape = shape,
enablePressIndication = false,
enableHoverIndication = false,
enableDragIndication = false,
),
)
} else {
Modifier
}
)
.border(2.dp, MaterialTheme.colorScheme.outline, shape)
.clip(shape)
// Pass ripple() explicitly to skip focus indication; the ring is drawn above.
.clickable(
interactionSource = interactionSource,
indication = ripple(enableFocusIndication = !hasInsetFocusRing),
onClick = { /* Do something */ },
)
.padding(12.dp),
contentAlignment = Alignment.Center,
) {
Text("Bordered clickable box")
}
}
}
ClickableWithDynamicFocusRingShapeSample
@Sampled
@Composable
fun ClickableWithDynamicFocusRingShapeSample() {
CompositionLocalProvider(
LocalRippleThemeConfiguration provides RippleDefaults.InsetFocusRingThemeConfiguration
) {
var isSelected by remember { mutableStateOf(false) }
val primaryColor = MaterialTheme.colorScheme.primary
val tertiaryColor = MaterialTheme.colorScheme.tertiary
// ColorProducer reads state during draw, avoiding recomposition when the ripple color
// changes, and preserving the existing ripple state instead of recreating the ripple.
val colorProducer =
remember(primaryColor, tertiaryColor) {
ColorProducer { if (isSelected) primaryColor else tertiaryColor }
}
val shape = if (isSelected) MaterialTheme.shapes.large else MaterialTheme.shapes.small
// Delegate from a single Shape instance to the changing shape to preserve the ripple node.
val focusRingShape = remember {
object : Shape {
var currentShape: Shape = shape
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density,
): Outline = currentShape.createOutline(size, layoutDirection, density)
}
}
focusRingShape.currentShape = shape
Box(
modifier =
Modifier.fillMaxWidth()
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceContainer)
.clickable(
interactionSource = null,
indication = ripple(color = colorProducer, focusRingShape = focusRingShape),
onClick = { isSelected = !isSelected },
)
.padding(12.dp),
contentAlignment = Alignment.Center,
) {
Text(if (isSelected) "Selected" else "Unselected")
}
}
}