Is there a way to disable the indication animation on the Composable Checkbox?
The typical path of adding the indication = null parameter to the .clickable Modifier doesn't appear to work.
When I looked in the documentation it just directed me to the different modifiers.
Checkbox Composable Documentation
Checkbox(
checked = checkedState.value,
onCheckedChange = {vm.HandleListItemClick(optionItems, i, checkedState)},
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null,
enabled = true,
onClickLabel = "${optionItems[i].label} checkbox selected status is ${checkedState.value}",
role = null,
){},
enabled = true,
)
It doesn't work since the Checkbox defines a custom indication inside the implementation.
You can provide a custom LocalRippleTheme to override the default behaviour.
Something like:
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
val checkedState = remember { mutableStateOf(true) }
Checkbox(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
private object NoRippleTheme : RippleTheme {
#Composable
override fun defaultColor() = Color.Unspecified
#Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f,0.0f,0.0f,0.0f)
}
Related
How to set Background Color for Material3 Card in Android Compose?
Piggy backing fro this question. The answers tells how to set a background color.
When material3 card is pressed, it changes color with a ripple effect.
But how can I change the effect color when it is pressed?
CardDefaults.cardColors(....) doesn't do it
The Card with the onClick variant uses internally an indication = rememberRipple(). This creates and remembers a Ripple using values provided by RippleTheme.
You can provide a custom LocalRippleTheme to override the default behaviour:
CompositionLocalProvider(LocalRippleTheme provides GreenRippleTheme) {
Card(
onClick = { /* Do something */ },
modifier = Modifier.size(width = 180.dp, height = 100.dp)
) {
//Card content
}
}
with:
private object GreenRippleTheme : RippleTheme {
#Composable
override fun defaultColor() = Color.Green
#Composable
override fun rippleAlpha(): RippleAlpha = RippleTheme.defaultRippleAlpha(
Color.Green,
lightTheme = true
)
}
Otherwise you can use the clickable modifier:
val interactionSource = remember { MutableInteractionSource() }
Card(
modifier = Modifier
.size(width = 180.dp, height = 100.dp)
.clickable (
onClick = { /* Do something */ },
interactionSource = interactionSource,
indication = rememberRipple(color = Green )
)
) {
//Card content
}
Finally if you want to modify the background color when the Card is pressed (not the ripple effect) you can use:
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val backgroundColor = if (isPressed) Yellow else MaterialTheme.colorScheme.surfaceVariant
Card(
interactionSource = interactionSource,
onClick = { /* Do something */ },
modifier = Modifier
.size(width = 180.dp, height = 100.dp),
colors = CardDefaults.cardColors(
containerColor = backgroundColor
)
) {
//Card content
}
You can use the "onClick" property of the Card component to change the color when it is pressed. To do this, you can define a state variable to track the current color of the card and toggle it on click. For example:
var cardColor by remember { mutableStateOf(Color.White) }
Card(
color = cardColor,
onClick = {
cardColor = if (cardColor == Color.White) Color.Green else Color.White
}
...
)
Alternatively, you can define the ripple color in the Modifier property of the Card component. For example:
Card(
color = Color.White,
modifier = Modifier.clickable(onClick = {
// logic to change color
}).ripple(color = Color.Green),
...
)
I am learning Jetpack Compose, and I've created a few set of buttons as a practice.
This is the button
#Composable
fun MyButton(
text: String,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
onClick: () -> Unit,
) {
Button(
enabled = isEnabled,
onClick = { onClick() },
modifier = modifier.width(270.dp).wrapContentHeight(),
) {
Text(
text = text,
style = MaterialTheme.typography.button
)
}
}
The problem is, that if i set the height of the button to wrapContentHeight or use heightIn with different max and min values, compose automatically adds a space around the button as seen here
But if i remove WrapContent, and use a fixed height, or define same min and max height for heightIn this probblem does not appear
#Composable
fun MyButton(
text: String,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
onClick: () -> Unit,
) {
Button(
enabled = isEnabled,
onClick = { onClick() },
modifier = modifier.width(270.dp).height(36.dp),
) {
Text(
text = text,
style = MaterialTheme.typography.button
)
}
}
And this is the code used for the column/preview of the functions:
#Composable
private fun SampleScreen() {
MyTheme{
Surface(modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background,){
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically),
modifier = Modifier.padding(20.dp)
) {
var isEnabled by remember { mutableStateOf(false) }
MyButton("Enable/Disable") {
isEnabled = !isEnabled
}
MyButton("Button") {}
MyButton(text = "Disabled Button", isEnabled = isEnabled) {}
}
}
}
}
Even if I remove the spacedBy operator from column the same issue appears.
I have tried to search for an explanation to this, but I did not manage to find anything.
Any help or resource with explanations is appreciated.
This is because Minimum dimension of Composables touch area is 48.dp by default for accessibility. However you can override this by using
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
Button(
enabled = isEnabled,
onClick = { onClick() },
modifier = modifier.heightIn(min = 20.dp)
.widthIn(min = 20.dp),
) {
Text(
text = text,
style = MaterialTheme.typography.button
)
}
}
Or something like
Button(modifier = Modifier.absoluteOffset((-12).dp, 0.dp)){
Text(
text = text,
style = MaterialTheme.typography.button
)
}
I would like to use the TabRow, but when I click the background has a ripple effect that I do not want. Is there a way to change this? I have the Tab's selectedContectColor equal to the same background color of the page, but I still see a white ripple effect.
TabRow(
modifier = Modifier.height(20.dp),
selectedTabIndex = selectedIndex,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
modifier = Modifier.customTabIndicatorOffset(
currentTabPosition = tabPositions[lazyListState.firstVisibleItemIndex]
tabWidths[lazyListState.firstVisibleItemIndex]
),
color = RED
)
},
backgroundColor = BLACK
) {
tabList.forEachIndexed{ index, tab ->
val selected = (selectedIndex == index)
Tab(
modifier = Modifier
// Have tried different solutions here where there is a .clickable
// and the indication = null, and set interactionSource = remember{
//MutableInteractionSource()}
selected = selected,
selectedContentColor = BLACK,
onClick = {
animateScrollToItem(selectedIndex)
},
text = {
Text("Text Code")
}
)
}
}
You can see in these docs that the selectedContentColor affects the ripple
The ripple is implemented in a selectable modifier defined inside the Tab.
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 to your composable with the LocalRippleTheme.
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
//..TabRow()
}
private object NoRippleTheme : RippleTheme {
#Composable
override fun defaultColor() = Color.Unspecified
#Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f,0.0f,0.0f,0.0f)
}
The shimmer effect is handled by the indication property.
Put it inside the clickable section.
You can create an extension function
inline fun Modifier.noRippleClickable(crossinline onClick: ()->Unit): Modifier = composed {
clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
then simply replace Modifier.clickable {} with Modifier.noRippleClickable {}
Traditional EditText on Android supports android:selectAllOnFocus attribute, which causes its content to be selected when user, for example, clicks on the EditText.
How can this behavior be achieved when using androidx.compose.material.TextField in Jetpack Compose?
You can collect the focus state from MutableInteractionSource and change the selection state depending on it:
var textFieldValue by remember { mutableStateOf(TextFieldValue("Lorem ipsum")) }
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
LaunchedEffect(isFocused) {
textFieldValue = textFieldValue.copy(
selection = if (isFocused) {
TextRange(
start = 0,
end = textFieldValue.text.length
)
} else {
TextRange.Zero,
}
)
}
TextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
interactionSource = interactionSource,
)
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
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()
}
}
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
I used #Mahdi-Malv's answer and modify as below:
remove inline and crossinline
modify according to my requirement
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,
)