In Jetpack Compose, when you enable clickable {}
on a modifier for a composable, by default it enables ripple effect for it. How to disable this behavior?
Example code
Row(modifier = Modifier
.clickable { // action }
)
In Jetpack Compose, when you enable clickable {}
on a modifier for a composable, by default it enables ripple effect for it. How to disable this behavior?
Example code
Row(modifier = Modifier
.clickable { // action }
)
Short answer:
to disable the ripple pass null
in the indication
parameter in the clickable
modifier:
val interactionSource = remember { MutableInteractionSource() }
Column {
Text(
text = "Click me without any ripple!",
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null
) {
/* doSomething() */
}
)
Why it doesn't work with some Composables as Buttons:
Note that in some Composables, like Button
or IconButton
, it doesn't work since the indication
is defined internally by the component which uses indication = rememberRipple()
. This creates and remembers a Ripple
using values provided by RippleTheme
.
In this cases you can't disable it but you can change the appearance of the ripple that is based on a RippleTheme
. You can define a custom RippleTheme and apply it to your composable with the LocalRippleTheme
.
Something like:
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
Button(
onClick = { /*...*/ },
) {
//...
}
}
with:
private object NoRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f,0.0f,0.0f,0.0f)
}
Custom modifier
If you prefer you can build your custom Modifier with the same code above, you can use:
fun Modifier.clickableWithoutRipple(
interactionSource: MutableInteractionSource,
onClick: () -> Unit
) = composed(
factory = {
this.then(
Modifier.clickable(
interactionSource = interactionSource,
indication = null,
onClick = { onClick() }
)
)
}
)
and then just apply it:
Row(
modifier = Modifier
.clickableWithoutRipple(
interactionSource = interactionSource,
onClick = { doSomething() }
)
){
//Row content
}
Long answer:
If you add the clickable
modifier to a composable to make it clickable within its bounds it will show an Indication
as specified in indication parameter.
By default, indication from LocalIndication
will be used.
If you are using a MaterialTheme
in your hierarchy, a Ripple
, defined by rememberRipple()
, will be used as the default Indication
inside components such as androidx.compose.foundation.clickable
and androidx.compose.foundation.indication
.
Use this Modifier extension:
fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed {
clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
then simply replace Modifier.clickable {}
with Modifier.noRippleClickable {}
Row(modifier = Modifier.noRippleClickable {
// action
})
To disable the ripple effect, have to pass null
to indication
property of the modifier.
More about indication on Jetpack Compose documentation
Code
Row(
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() } // This is mandatory
) {
// action
}
)
You can handle it this way when working with Buttons.
Create a Ripple interactionSource class
class NoRippleInteractionSource : MutableInteractionSource {
override val interactions: Flow<Interaction> = emptyFlow()
override suspend fun emit(interaction: Interaction) {}
override fun tryEmit(interaction: Interaction) = true
}
In case of a button, you can handle it by passing the ripple interaction class as the interactionSource parameter i.e:
Button(
onClick = { /*...*/ },
interactionSource = NoRippleInteractionSource()
) {
//..
}
This solution works with all compossables that accept a mutableInteractionSource as a parameter for example Button(), TextButton(), Switch(), etc
When working the clickable modifier you can do it this way:
modifier = Modifier.clickable(
indication = null,
interactionSource = NoRippleInteractionSource()
) { /** todo action here */ }
You can also go a further step to modify the above answer and create an extension function and re use for all your clickable functions i.e:
fun Modifier.noRippleClick(onClick:()->Unit):Modifier {
return this.clickable(
interactionSource = NoRippleInteractionSource(),
indication = null
){
onClick()
}
}
You can now use the extension modifier in place of the clickable modifier i.e:
modifier = modifier.noRippleClick { /** todo action here */ }
Modifier extension with other parameters :
inline fun Modifier.noRippleClickable(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
crossinline onClick: ()->Unit
): Modifier = composed {
clickable(
enabled = enabled,
indication = null,
onClickLabel = onClickLabel,
role = role,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
I used @Mahdi-Malv's answer and modify as below:
fun Modifier.noRippleClickable( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, indication: Indication? = null, enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onClick: () -> Unit, ) = clickable( interactionSource = interactionSource, indication = indication, enabled = enabled, onClickLabel = onClickLabel, role = role, onClick = onClick, )
With androidx.compose.foundation
there is a enabled
attribute inside clickable extension. I think that it is easiest way. Link
fun Modifier.clickable(
interactionSource: MutableInteractionSource,
indication: Indication?,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
): Modifier