Modify ripple color of IconButton in Jetpack Compose
Asked Answered
R

4

29

How can I change the ripple color of an IconButton?

I tried doing it this way, but it doesn't change:

IconButton(
    onClick = { onClick() },
    modifier = Modifier.clickable(
        onClick = { onClick() },
        indication = rememberRipple(color = MyCustomTheme.colors.primary),
        interactionSource =  remember { MutableInteractionSource() },
    )
)
Rustin answered 26/4, 2021 at 10:54 Comment(0)
D
43

I don't know if you found a way to make it work for the whole app but I found a way to do so. So I'm posting this incase someone else has a similar issue.

You can set the custom RippleTheme object as described by Gabriele Mariotti's answer then you can pass the CompositionLocalProvider() as content in MaterialTheme. The content from the app's theme can then be set as content for CompositionalLocalProvider().

Take a look here:

private object JetNewsRippleTheme : RippleTheme {
    // Here you should return the ripple color you want
    // and not use the defaultRippleColor extension on RippleTheme.
    // Using that will override the ripple color set in DarkMode
    // or when you set light parameter to false
    @Composable
    override fun defaultColor(): Color = MaterialTheme.colors.primary

    @Composable
    override fun rippleAlpha(): RippleAlpha = RippleTheme.defaultRippleAlpha(
        Color.Black,
        lightTheme = !isSystemInDarkTheme()
    )
}

Then for your app theme it should be:

@Composable
fun JetNewsTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        typography = JetNewsTypography,
        shapes = JetNewsShapes
    ) {
        CompositionLocalProvider(
            LocalRippleTheme provides JetNewsRippleTheme,
            content = content
        )
    }
}

This method should work for the whole app unless you explicitly set another RippleTheme directly to Composables below the AppTheme hierarchy. And it doesn't conflict with other types of ComposableLocalProvider values you may set directly to your other Composables.

Drool answered 1/8, 2021 at 12:9 Comment(0)
T
24

Your code doesn't work since the Ripple is implemented in a .clickable modifier defined inside the IconButton.

The appearance of the Ripple is based on a RippleTheme. You can define a custom RippleTheme and apply it to your composable with LocalRippleTheme.

Something like:

private object RippleCustomTheme: RippleTheme {

    //Your custom implementation...
    @Composable
    override fun defaultColor() =
        RippleTheme.defaultRippleColor(
            Color.Red, 
            lightTheme = true
        )

    @Composable
    override fun rippleAlpha(): RippleAlpha =
        RippleTheme.defaultRippleAlpha(
            Color.Black,
            lightTheme = true
        )
}

and:

CompositionLocalProvider(LocalRippleTheme provides RippleCustomTheme) {
    IconButton(
        onClick = { },
    ) {
        Icon(Icons.Filled.Add, "")
    }
}

Button with red rippleButton with purple ripple

Tribble answered 26/4, 2021 at 12:7 Comment(3)
Okay. So every time I need to modify the ripple, I need to wrap my composable with CompositionLocalProvider(LocalRippleTheme provides RippleCustomTheme) {}? Is it somehow possible to modify it once for the whole app?Rustin
You can define custom composable like your IconButton, Button.. that implement the code in the answer. To be honest I don't know if it is possible to define it globally.Tribble
While i don't think it's possible globally, there's a "hack": Most probably you define your theme - deriving from materialTheme - somewhere. Wrap the content block in the CompositionLocalProvider-Block from above. Since you will wrap literary everything with your customTheme, you will have wrapped everything with your RippleTheme, tooHissing
H
9

It's a bit late, but maybe this code will help someone.

To make your code work you need to apply interactionSource to the button itself too.

You can change the color locally in this way. It is important to apply height after calling the indication in order to keep the correct button and ripple dimensions.

Additionally, you need to apply a radius of any size in order to contain the radius inside of the button's shape (this seems to be a bug in Compose 1.2.0-rc03.

val interactionSource = remember { MutableInteractionSource() }
val rippleColor = Color.RED
val shape = RoundedCornerShape(size = 16.dp)

Button(
    modifier = Modifier
        .clip(shape = shape)
        .indication(
            interactionSource = interactionSource,
            indication = rememberRipple(
                color = rippleColor,
                radius = 8.dp
            )
        )
        .height(height = 40.dp),
    shape = shape,
    interactionSource = interactionSource,
)
Hayashi answered 19/7, 2022 at 11:9 Comment(0)
O
5

If you need just to override ripple color in one place you can create your own modifier for it.

fun Modifier.clickable(
    rippleColor: Color? = null,
    onClick: () -> Unit
) = composed(
    inspectorInfo = debugInspectorInfo {
        name = "clickable"
        properties["rippleColor"] = rippleColor
        properties["onClick"] = onClick
    }
) {
    Modifier.clickable(
        onClick = onClick,
        indication = rippleColor?.let {
            rememberRipple(
                color = it
            )
        } ?: LocalIndication.current,
        interactionSource = remember { MutableInteractionSource() }
    )
}
Orbit answered 17/2, 2023 at 10:42 Comment(1)
I'm getting the error "Modifier factory functions must use the receiver Modifier instance." You just have to add one more line at the end to make it chain-able: thisFisher

© 2022 - 2024 — McMap. All rights reserved.