Compose Ripple Customisation Guide

Fergus A Hewson
3 min readSep 15, 2023

This week at work, I was tasked to style ripples for the new Compose screens!

Design wanted a default style and 4 overridable styles to use in different contexts throughout the App.

DEFAULT, RED, BLUE, REG, ORANGE

The first solution that came to mind was using a custom Indication to change the ripple color!

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(200.dp, 200.dp)
.border(2.dp, Color.Black)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = Color.Red),
onClick = { /* wooo*/ }
)
)
{}
}

Easy! Then off to make it reusable by making a custom Modifier that accepts a color as the arguments…

fun Modifier.clickWithRedRipple(color: Color, onClick: () -> Unit): Modifier = composed {
this.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = color),
onClick = onClick
)
}

Now we can specify what ever Ripple color we like as a modifier on a composable!

.clickableWithRipple(Color.Red) { /* woosh */ }
.clickableWithRipple(Color.Green) { /* woosh */ }
.clickableWithRipple(Color.Blue) { /* woosh */ }
.clickableWithRipple(Color.Magenta) { /* woosh */ }

Happy as, I trot off to design to show them my work, thinking this was an easy win for the team!

However, they required DARKER Ripples!

Back I trot, to the drawing board!

With no obvious way to increase the Alpha on the Indicator Ripple I was stumped.

After some digging I discovered the RippleTheme and how it could be used with CompositionLocalProvider to change the Ripple Color/Alpha.

So I made a RippleTheme with full RippleAlpha.

@Immutable
object RedRipple : RippleTheme {
@Composable
override fun defaultColor() = Color.Red

@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(1f, 1f, 1f, 1f)
}

Then wrapped my clickable with a CompositionLocalProvider that provided the full Alpha RippleTheme.

CompositionLocalProvider(LocalRippleTheme provides RedRipple ) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(200.dp, 200.dp)
.border(2.dp, Color.Black)
.clickable { /* woosh */ }
){}
}
}
Darker red ripple.

Woohoo! Full Red-Ripple! Well … as full as you can get till the RippleDrawable clamps the Alpha on you … I found this little gem in the material ripple library that explains it all.

RippleHostView.android.kt : 380 : material-ripple:1.4.1@aar
// Note: above 28 the ripple alpha is clamped to 50%, so this might not be the
// _actual_ alpha that is used in the ripple.

Now we have maxed out the alpha but at the expense of boilerplate and the cool reusable pattern of Modifier extensions functions.

Determined to keep a clean pattern and interested to see why the Indicator Ripple was lighter … I dug in to the Material Ripple code and found the Alpha on the Ripples generated from the Indicator pattern was extracted from the RippleTheme provided in the CompositionLocalProvider.

Ripple.kt : 125 : material-ripple:1.4.1@aar

val theme = LocalRippleTheme.current
val color = rememberUpdatedState(
if (color.value.isSpecified) {
color.value
} else {
theme.defaultColor()
}
)
val rippleAlpha = rememberUpdatedState(theme.rippleAlpha())

Idea … If we provide a RippleTheme at the root of the MaterialTheme with full Alpha we will get full Alpha whilst using the Indicator pattern. Allowing us to get rid of the LocalCompositionProvider boiler plate!!!

Here is the full Alpha ripple which is copied from the MaterialRipple … but with the Alpha at 100!

@Immutable
object FullAlphaRipple : RippleTheme {
@Composable
override fun defaultColor() = LocalContentColor.current

@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(1f, 1f, 1f, 1f)
}

Next to provide our ripple in the MaterialTheme

MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
) {
CompositionLocalProvider(LocalRippleTheme provides FullAlphaRipple) {
content()
}
}
// Please don't use this code, it misses a lot of setup that is in the MaterialTheme
// Look into the material theme and see all the differen providers that you will miss out on.
// If you do this, make sure you provide ALL-THE-THINGS the material theme does.
// This is just a brief example!

There we have it! Now our Indicator/Modifier pattern will work with full alpha anywhere in the App, saving boiler plate and making developers lives easier!

Note: this will turn up the Alpha on all your Ripples too! Beware!

This works for both Material 2/3!

Happy Composing :)

--

--