Mor Amit
04/21/2022, 9:40 AMbounce
function, an extension of Modifier. The bounce
function receives a shouldBounce
boolean that indicates if I should start the animation.
I wonder if there is a better way to do that and move the shouldBounce
boolean into the Modifier. With my current implementation, I need to define a shouldBounce
boolean in every place that wants to use my bounce function.
Maybe there is a way to catch the click event on both Box and Modifier?
I attached some code examples in the first reply@Composable
fun SaveButton(saved: Boolean = true, modifier: Modifier, onClickListener: () -> Unit ) {
val interactionSource = remember { MutableInteractionSource() }
var shouldBounce by remember { mutableStateOf(false) }
Box(
modifier = modifier
.size(40.dp)
.background(
color = Color.Black,
shape = CircleShape
)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {
onClickListener()
shouldBounce = true
}
)
) {
SaveIcon(
saved = saved,
modifier = Modifier
.padding(12.dp)
.fillMaxSize(),
shouldBounce = shouldBounce,
onBounceAnimationEnd = {
shouldBounce = false
}
)
}
}
The image
@Composable
fun SaveIcon(saved: Boolean = true, modifier: Modifier, shouldBounce: Boolean = false, onBounceAnimationEnd: () -> Unit) {
val savedRes = R.drawable.save_with_border
val saveRes = R.drawable.save_outline_with_border
Image(
painterResource(if (saved) savedRes else saveRes),
contentDescription = "Save",
modifier = modifier.bounce(
bounce = shouldBounce,
finishedListener = { onBounceAnimationEnd() }
)
)
}
bounce func:
fun Modifier.bounce(
shouldBounce: Boolean = false,
finishedListener: ((Float) -> Unit)? = null
) = composed {
val bounceFloat by animateFloatAsState(
targetValue = if (shouldBounce) 1.25f else 1f,
animationSpec = repeatable(
iterations = 1, // iteration count
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
),
finishedListener = finishedListener
)
graphicsLayer(
scaleY = if (shouldBounce) bounceFloat else 1f,
scaleX = if (shouldBounce) bounceFloat else 1f
)
}
The way I use it inside the screen
@Composable
fun SaveButtonScreen() {
var saved by rememberSaveable{ mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)) {
SaveButton(
modifier = Modifier,
saved = saved,
onClickListener = { saved = !saved})
}
}
myanmarking
04/21/2022, 9:52 AMfun Modifier.bounce(onClick: () -> Unit): Modifier {
return this.then(
Modifier.composed {
val bounce = remember { mutableStateOf(false) }
clickable(
enabled = bounce.value,
onClick = {
bounce.value = false,
onClick()
}
)
}
)
}
?Mor Amit
04/21/2022, 9:54 AMChris Sinco [G]
04/21/2022, 4:02 PMMor Amit
04/21/2022, 4:10 PMChris Sinco [G]
04/21/2022, 4:53 PM@Preview
@Composable
fun BounceButton(
favorite: Boolean = false
) {
val scope = rememberCoroutineScope()
val animatedScale = remember { Animatable(1f) }
Button(
onClick = {
scope.launch {
animatedScale.animateTo(targetValue = 1.25f, animationSpec = tween(300))
animatedScale.animateTo(targetValue = 1f, animationSpec = tween(300))
}
}
) {
Icon(
imageVector = if (favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
contentDescription = null,
modifier = Modifier.scale(animatedScale.value)
)
}
}
Mor Amit
04/21/2022, 5:18 PMChris Sinco [G]
04/21/2022, 5:33 PMfun Modifier.bounceContent(
onClick: () -> Unit = {}
) = composed {
val scope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }
val animatedScale = remember { Animatable(1f) }
clickable(
interactionSource = interactionSource,
indication = null,
onClick = {
scope.launch {
animatedScale.animateTo(targetValue = 1.25f, animationSpec = tween(300))
animatedScale.animateTo(targetValue = 1f, animationSpec = tween(300))
}
onClick()
}
)
.graphicsLayer(
scaleY = animatedScale.value,
scaleX = animatedScale.value
)
}
@Preview
@Composable
fun BounceSample(
favorite: Boolean = false
) {
Column {
Box(
modifier = Modifier
.size(40.dp)
.background(Color.Black, CircleShape)
.bounceContent { println("bounce heart") },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.padding(12.dp)
.fillMaxSize(),
)
}
Column(
modifier = Modifier
.background(Color.Yellow, RoundedCornerShape(4.dp))
.padding(horizontal = 8.dp, vertical = 4.dp)
.bounceContent { println("bounce column")}
) {
Text("This text will grow")
Text("This text will grow")
}
}
}
Mor Amit
04/22/2022, 7:04 PMChris Sinco [G]
04/26/2022, 11:31 PMZach Klippenstein (he/him) [MOD]
04/27/2022, 7:33 PMgraphicsLayer
modifier), that layer is used for all drawing and coordinate transforms for everything below it: any modifiers that come after it on the current element, and any elements that are a child of the modified one. You can put a graphics layer at the root of your app and any transformations applied to it will be applied to everything in your app.Mor Amit
04/28/2022, 7:16 AM