How to Ripple Outside of Compose Material

There is more to life than Material Design.

After all, it is a common complaint, at least here in the US, that designers design aroundiOS. Last I checked, iOS designs are not very Material. So, it stands to reason thatAndroid developers using Compose will need to deviate from Material Design in many cases.

In theory, developers would create custom design systems from the ground up. In practice,I suspect that most developers use Compose Material or Material3 and try to implement the designsthat way. This can work, but sometimes developers wind up having to resort to some fairlysignificant hacks in order to work around cases where Material���s opinions deviate fromthe designers��� opinions.

I am working on a project where I am creating a from-scratch custom design system.Along the way, I will try to point out how I am filling in various Compose gaps, whereMaterial provides a solution but ���you���re on your own��� for a custom design system.

This time, let���s look at indications. Indications are how a clickable composable letsthe user know that, indeed, they clicked the composable. Material Design calls for aripple effect. Compose Material offers the ripple.

Your graphic designer probably is not designing indications for you, but you are goingto need some sort of touch feedback. The ripple isa perfectly cromulentoption, but the ripple is a Compose Material thing and will not be supplied ���out of the box���for non-Material design systems.

The good news is that a lot of the ripple logic resides in a standalone library, onewithout other Material dependencies. This makes it reasonable for use in a non-MaterialCompose app. The bad news is that the documentation on how to actually apply thatlibrary is limited.

So, here is how you can do it.

First, you will need to add that library. That is androidx.compose.material:material-ripple,and the version I wrote this blog post around is 1.7.2:

composeRipple = "1.7.2"compose-ripple = { group = "androidx.compose.material", name = "material-ripple", version.ref = "composeRipple" }dependencies { implementation(libs.compose.ripple) // TODO other cool libraries go here}

The documentation tells you ���oh, do what Material3 does to provide the ripple���. That iscontained mostly in Ripple.kt.So, copy its contents into your own project (be sure to abide by the license!). Thiswill require you to also grab these StateTokens defined elsewhere:

internal object StateTokens { const val DraggedStateLayerOpacity = 0.16f const val FocusStateLayerOpacity = 0.1f const val HoverStateLayerOpacity = 0.08f const val PressedStateLayerOpacity = 0.1f}

For the file version I used, IIRCthere is only one real connection to the rest of Material3 in the file: a referenceto currentValueOf(LocalContentColor). This is only needed if you do not supply a colordirectly when creating the ripple or via a LocalRippleConfiguration composition local.In my case, I was perfectly happy with two options for providing a color and did notneed a third, so I swapped currentValueOf(LocalContentColor) with a RuntimeException:

private fun attachNewRipple() { val calculateColor = ColorProducer { val userDefinedColor = color() if (userDefinedColor.isSpecified) { userDefinedColor } else { // If this is null, the ripple will be removed, so this should always be non-null in // normal use val rippleConfiguration = currentValueOf(LocalRippleConfiguration) if (rippleConfiguration?.color?.isSpecified == true) { rippleConfiguration.color } else { // currentValueOf(LocalContentColor) throw RuntimeException("missing color for ripple") } } }

That should be all the changes that are needed��� at least for the version of Ripple.ktthat I used. It is possible that I forgot something, in which case I apologize.

Then, to actually apply the ripple, set the LocalIndication composition local toa ripple() that you create from the Ripple.kt code that you copied and revised:

CompositionLocalProvider(LocalIndication provides ripple(color = Color.White)) { // TODO cool stuff goes here}

If you want to be able to change the ripple color without replacing the entireripple, rather than provide the color to ripple(), you could leave that parameterout and also define a LocalRippleConfiguration:

CompositionLocalProvider( LocalIndication provides ripple(), LocalRippleConfiguration provides RippleConfiguration(color = Color.White)) { // TODO cool stuff goes here}

RippleConfiguration also lets you control the alpha values used in the rippleeffect, overriding the defaults coming from StateTokens.

No matter how you provide the color, you will need to do that separately if you needdifferent colors for different themes, such as light and dark.

If you are using ComposeThemefor your custom design system, there is an indication property that you can set in yourbuildComposeTheme() builder:

private val AwesomeDarkTheme = buildComposeTheme { name = "AwesomeDarkTheme" indication = ripple(color = Color.White) // TODO define the rest of the theme, which hopefully also is cool}

ComposeTheme will take care of setting LocalIndication for you.

Once LocalIndication is set, clickable() and other modifiers and code will applyit automatically, so your ripple should show up.Alternatively, you can provide a ripple() directly as the indicationto clickable(), perhaps for cases where you want a different implementation than theone from LocalIndication.

If you want an indication, but you want to do something other than a ripple��� lookat the implementation of ripple(), especially the RippleNodeFactory andDelegatingThemeAwareRippleNode, which eventually link into code from that material-ripplelibrary mentioned earlier.

 •  0 comments  •  flag
Share on Twitter
Published on September 25, 2024 16:13
No comments have been added yet.