How to make FlipCard animation in Jetpack compose - android

I have an existing app where I have implemented FlipCard animation like below using Objectanimator in XML. If I click on a card it flips horizontally. But now I want to migrate it to jetpack compose. So is it possible to make flip card animation in jetpack compose?
Update
Finally, I have ended up with this. Though I don't know if it is the right way or not but I got exactly what I wanted. If there is any better alternative you can suggest. Thank you.
Method 1: Using animate*AsState
#Composable
fun FlipCard() {
var rotated by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(
targetValue = if (rotated) 180f else 0f,
animationSpec = tween(500)
)
val animateFront by animateFloatAsState(
targetValue = if (!rotated) 1f else 0f,
animationSpec = tween(500)
)
val animateBack by animateFloatAsState(
targetValue = if (rotated) 1f else 0f,
animationSpec = tween(500)
)
val animateColor by animateColorAsState(
targetValue = if (rotated) Color.Red else Color.Blue,
animationSpec = tween(500)
)
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Card(
Modifier
.fillMaxSize(.5f)
.graphicsLayer {
rotationY = rotation
cameraDistance = 8 * density
}
.clickable {
rotated = !rotated
},
backgroundColor = animateColor
)
{
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = if (rotated) "Back" else "Front",
modifier = Modifier
.graphicsLayer {
alpha = if (rotated) animateBack else animateFront
rotationY = rotation
})
}
}
}
}
Method 2: Encapsulate a Transition and make it reusable.
You will get the same output as method 1. But it is reusable and for the complex case.
enum class BoxState { Front, Back }
#Composable
fun AnimatingBox(
rotated: Boolean,
onRotate: (Boolean) -> Unit
) {
val transitionData = updateTransitionData(
if (rotated) BoxState.Back else BoxState.Front
)
Card(
Modifier
.fillMaxSize(.5f)
.graphicsLayer {
rotationY = transitionData.rotation
cameraDistance = 8 * density
}
.clickable { onRotate(!rotated) },
backgroundColor = transitionData.color
)
{
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = if (rotated) "Back" else "Front",
modifier = Modifier
.graphicsLayer {
alpha =
if (rotated) transitionData.animateBack else transitionData.animateFront
rotationY = transitionData.rotation
})
}
}
}
private class TransitionData(
color: State<Color>,
rotation: State<Float>,
animateFront: State<Float>,
animateBack: State<Float>
) {
val color by color
val rotation by rotation
val animateFront by animateFront
val animateBack by animateBack
}
#Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
val transition = updateTransition(boxState, label = "")
val color = transition.animateColor(
transitionSpec = {
tween(500)
},
label = ""
) { state ->
when (state) {
BoxState.Front -> Color.Blue
BoxState.Back -> Color.Red
}
}
val rotation = transition.animateFloat(
transitionSpec = {
tween(500)
},
label = ""
) { state ->
when (state) {
BoxState.Front -> 0f
BoxState.Back -> 180f
}
}
val animateFront = transition.animateFloat(
transitionSpec = {
tween(500)
},
label = ""
) { state ->
when (state) {
BoxState.Front -> 1f
BoxState.Back -> 0f
}
}
val animateBack = transition.animateFloat(
transitionSpec = {
tween(500)
},
label = ""
) { state ->
when (state) {
BoxState.Front -> 0f
BoxState.Back -> 1f
}
}
return remember(transition) { TransitionData(color, rotation, animateFront, animateBack) }
}
Output

setContent {
ComposeAnimationTheme {
Surface(color = MaterialTheme.colors.background) {
var state by remember {
mutableStateOf(CardFace.Front)
}
FlipCard(
cardFace = state,
onClick = {
state = it.next
},
axis = RotationAxis.AxisY,
back = {
Text(text = "Front", Modifier
.fillMaxSize()
.background(Color.Red))
},
front = {
Text(text = "Back", Modifier
.fillMaxSize()
.background(Color.Green))
}
)
}
}
}
enum class CardFace(val angle: Float) {
Front(0f) {
override val next: CardFace
get() = Back
},
Back(180f) {
override val next: CardFace
get() = Front
};
abstract val next: CardFace
}
enum class RotationAxis {
AxisX,
AxisY,
}
#ExperimentalMaterialApi
#Composable
fun FlipCard(
cardFace: CardFace,
onClick: (CardFace) -> Unit,
modifier: Modifier = Modifier,
axis: RotationAxis = RotationAxis.AxisY,
back: #Composable () -> Unit = {},
front: #Composable () -> Unit = {},
) {
val rotation = animateFloatAsState(
targetValue = cardFace.angle,
animationSpec = tween(
durationMillis = 400,
easing = FastOutSlowInEasing,
)
)
Card(
onClick = { onClick(cardFace) },
modifier = modifier
.graphicsLayer {
if (axis == RotationAxis.AxisX) {
rotationX = rotation.value
} else {
rotationY = rotation.value
}
cameraDistance = 12f * density
},
) {
if (rotation.value <= 90f) {
Box(
Modifier.fillMaxSize()
) {
front()
}
} else {
Box(
Modifier
.fillMaxSize()
.graphicsLayer {
if (axis == RotationAxis.AxisX) {
rotationX = 180f
} else {
rotationY = 180f
}
},
) {
back()
}
}
}
}
Check this article. https://fvilarino.medium.com/creating-a-rotating-card-in-jetpack-compose-ba94c7dd76fb.

Related

Bounce Button Animation in Compose

I want to make a button like this in Compose:
https://pub.dev/packages/flutter_bounceable
But the clickable method is not work in my code.
I tried with this code, but it has an error.
Pushing the button, but there's no action.
Animations are working well, but not for the clickable.
fun Modifier.bounceClick(onClick: () -> Unit,animationDuration: Int = 100,
scaleDown: Float = 0.9f) = composed {
val interactionSource = MutableInteractionSource()
val coroutineScope = rememberCoroutineScope()
val scale = remember {
Animatable(1f)
}
this
.scale(scale = scale.value)
.background(
color = Color(0xFF35898F),
shape = RoundedCornerShape(size = 12f)
)
.clickable(interactionSource = interactionSource, indication = null, onClick = onClick)
.pointerInput(Unit) {
while(true)
awaitPointerEventScope {
awaitFirstDown()
coroutineScope.launch {
scale.animateTo(
scaleDown,
animationSpec = tween(animationDuration),
)
}
waitForUpOrCancellation()
coroutineScope.launch {
scale.animateTo(
scaleDown,
animationSpec = tween(20),
)
scale.animateTo(
1f,
animationSpec = tween(animationDuration),
)
}
}
}
}
This is quite simple to do with Compose.
You should use foreachGesture or awaitEachGesture if Compose version is 1.4.0-alpha03 with Modifier.pointerInput instead of while. Also when you have clickable you don't need Modifier.pointerInput as well , you can use either of them.
I will only demonstrate how to do it with Modifier.clickable and interactionSource.collectIsPressedAsState() as below.
Result
Implementation
fun Modifier.bounceClick(
animationDuration: Int = 100,
scaleDown: Float = 0.9f,
onClick: () -> Unit
) = composed {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val animatable = remember {
Animatable(1f)
}
LaunchedEffect(key1 = isPressed) {
if (isPressed) {
animatable.animateTo(scaleDown)
} else animatable.animateTo(1f)
}
Modifier
.graphicsLayer {
val scale = animatable.value
scaleX = scale
scaleY = scale
}
.clickable(
interactionSource = interactionSource,
indication = null
) {
onClick()
}
}
Usage
#Composable
private fun BounceExample() {
Row {
Box(
Modifier
.background(Color.Red, RoundedCornerShape(10.dp))
.bounceClick {
}
.padding(10.dp),
contentAlignment = Alignment.Center
) {
Text(text = "Hello World", color = Color.White, fontSize = 20.sp)
}
Spacer(modifier = Modifier.width(10.dp))
Box(
Modifier
.bounceClick {
}
.background(Color.Green, RoundedCornerShape(10.dp))
.padding(10.dp),
contentAlignment = Alignment.Center
) {
Text(text = "Hello World", color = Color.White, fontSize = 20.sp)
}
}
}

push an animation in composable function to start again whenever I click a button/ JetPack/ Kotlin

**every time the user click the button i want to show different icon inside the box and the transition should start again from its initial state **
fun card(iconColor: Color){
val animateShape = remember { Animatable(50f) }
Card(
modifier = Modifier
.size(100.dp)
.padding(5.dp)
.border(width = Dp(animateShape.value), White, shape = CircleShape),
shape = CircleShape,
elevation = 5.dp,
backgroundColor = BlueGrey,
) {
Icon(
imageVector = Icons.Default.DoneAll,
contentDescription = null,
tint = iconColor,
modifier = Modifier
.padding(12.dp)
)
}
LaunchedEffect(iconColor) {
animateShape.animateTo(
targetValue = 2f,
animationSpec = repeatable(
animation = tween(
durationMillis = 3000,
easing = LinearEasing,
delayMillis = 500
),
iterations = 1
)
)
}
}```
I'm not quite sure, but are you looking for something like this?, every click it changes the Icon with scaleIn/scaleOut animation
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun SwitchIconOnClick() {
var iconState by remember { mutableStateOf("State_1") }
AnimatedContent(
targetState = iconState,
contentAlignment = Alignment.Center,
transitionSpec = {
scaleIn(animationSpec = tween(durationMillis = 200)) with
scaleOut(animationSpec = tween(durationMillis = 200))
}
) { state ->
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.clickable {
when (state) {
"State_1" -> {
iconState = "State_2"
}
"State_2" -> {
iconState = "State_3"
}
"State_3" -> {
iconState = "State_1"
}
}
},
contentAlignment = Alignment.Center
) {
Icon(imageVector = when (state) {
"State_1" -> {
Icons.Rounded.NotificationAdd
}
"State_2" -> {
Icons.Rounded.ClosedCaption
}
"State_3" -> {
Icons.Rounded.Delete
}
else -> {
Icons.Rounded.DataArray
}
},
contentDescription = null
)
}
}
}

SwipeToDismiss inside LazyColumn with animation

I am trying to achieve something like this but with Jetpack Compose. In other words swipe to delete like we could do in RecyclerView with ItemTouchHelper and class DiffCallBack : DiffUtil.ItemCallback<RvModel>() where we could see enter - exit animations and then the list moving gracefully up or down where the item has been inserted or removed.
This is what I have tried:
LazyColumn(state = listState) {
items(products, {listItem:InventoryEntity -> listItem.inventoryId}) { item ->
var unread by remember { mutableStateOf(false) }
val dismissState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToEnd) unread = !unread
it != DismissValue.DismissedToEnd
}
)
val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)
if (dismissState.isDismissed(DismissDirection.EndToStart)){
LaunchedEffect(Unit) {
delay(300)
viewModel.deleteProduct(item.inventoryId)
}
}
var itemAppeared by remember { mutableStateOf(!columnAppeared) }
LaunchedEffect(Unit) {
itemAppeared = true
}
AnimatedVisibility(
visible = itemAppeared && !isDismissed,
exit = shrinkVertically(
animationSpec = tween(
durationMillis = 300,
)
),
enter = expandVertically(
animationSpec = tween(
durationMillis = 300
)
)
) {
SwipeToDismiss(
state = dismissState,
modifier = Modifier.padding(vertical = 4.dp),
directions = setOf(
DismissDirection.StartToEnd,
DismissDirection.EndToStart
),
dismissThresholds = { direction ->
FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
},
background = {
val direction =
dismissState.dismissDirection ?: return#SwipeToDismiss
val color by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.Default -> Color.LightGray
DismissValue.DismissedToEnd -> Color.Green
DismissValue.DismissedToStart -> Color.Red
}
)
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> Alignment.CenterEnd
}
val icon = when (direction) {
DismissDirection.StartToEnd -> Icons.Default.Done
DismissDirection.EndToStart -> Icons.Default.Delete
}
val scale by animateFloatAsState(
if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f
)
Box(
Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = alignment
) {
Icon(
icon,
contentDescription = "Localized description",
modifier = Modifier.scale(scale)
)
}
},
dismissContent = {
Card(
elevation = animateDpAsState(
if (dismissState.dismissDirection != null) 4.dp else 0.dp
).value
) {
ProductRow(product = item, number = item.inventoryId)
}
}
)
}
}
}
Even though it works. Scrolling is not smooth, and when I scroll up it jumps to the top. What is the right way to implement this function?
Recently google anounced compose Version 1.1.0-beta03. Now we have a new way on how we can animate items. They have introduced a new modifier: Modifier.animateItemPlacement(). You may find latest compose version in this link.
I will try to post an example with minimum code so that you can reproduce it and see how you could achieve a SwipeToDismiss inside LazyColumn with animation.
Data Class To store information:
data class DataSet(
val itemId: Int,
val itemName: String,
val itemQty: String
)
Comparator to compare list items:
private val ListComparator = Comparator<DataSet> { left, right ->
left.itemId.compareTo(right.itemId)
}
The row of each of our items:
#Composable
fun ItemRow(
modifier: Modifier = Modifier,
product: DataSet,
number: Int
) {
Card(
shape = RoundedCornerShape(4.dp),
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
backgroundColor = Color.LightGray
) {
Row(modifier = modifier) {
Text(
text = "$number.", modifier = Modifier
.weight(2f)
.padding(start = 8.dp, end = 4.dp)
)
Text(
text = product.itemName, modifier = Modifier
.weight(10f)
.padding(end = 4.dp)
)
Text(
text = product.itemQty, modifier = Modifier
.weight(2f)
.padding(end = 4.dp)
)
}
}
}
Putting all together to our composable:
#ExperimentalMaterialApi
#ExperimentalFoundationApi
#Composable
fun helloWorld() {
var list by remember { mutableStateOf(listOf<DataSet>()) }
val comparator by remember { mutableStateOf(ListComparator) }
LazyColumn {
item {
Button(onClick = {
list = list + listOf(DataSet((0..1111).random(), "A random item", "100"))
}) {
Text("Add an item to the list")
}
}
val sortedList = list.sortedWith(comparator)
items(sortedList, key = { it.itemId }) { item ->
val dismissState = rememberDismissState()
if (dismissState.isDismissed(DismissDirection.EndToStart)) {
list = list.toMutableList().also { it.remove(item) } // remove
}
SwipeToDismiss(
state = dismissState,
modifier = Modifier
.padding(vertical = 1.dp)
.animateItemPlacement(),
directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart),
dismissThresholds = { direction ->
FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
},
background = {
val direction = dismissState.dismissDirection ?: return#SwipeToDismiss
val color by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.Default -> Color.LightGray
DismissValue.DismissedToEnd -> Color.Green
DismissValue.DismissedToStart -> Color.Red
}
)
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> Alignment.CenterEnd
}
val icon = when (direction) {
DismissDirection.StartToEnd -> Icons.Default.Done
DismissDirection.EndToStart -> Icons.Default.Delete
}
val scale by animateFloatAsState(
if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f
)
Box(
Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = alignment
) {
Icon(
icon,
contentDescription = "Localized description",
modifier = Modifier.scale(scale)
)
}
},
dismissContent = {
Card(
elevation = animateDpAsState(
if (dismissState.dismissDirection != null) 4.dp else 0.dp
).value
) {
ItemRow(
product = item,
number = item.itemId
)
}
}
)
}
}
}
For reference and see also other ways on how you could use Modifier.animateItemPlacement() you may check this example posted from Google.

Reset offset animation on draggable item in Jetpack Compose

I have a green square that I can drag vertically. But whenever I stop dragging it, I want it to reset the offset to the start with an animation. I tried it like this, but I can't figure it out. Does someone know how to do it?
#Composable
fun DraggableSquare() {
var currentOffset by remember { mutableStateOf(0F) }
val resetAnimation by animateIntOffsetAsState(targetValue = IntOffset(0, currentOffset.roundToInt()))
var shouldReset = false
Box(contentAlignment = Alignment.TopCenter, modifier = Modifier.fillMaxSize()) {
Surface(
color = Color(0xFF34AB52),
modifier = Modifier
.size(100.dp)
.offset {
when {
shouldReset -> resetAnimation
else -> IntOffset(0, currentOffset.roundToInt())
}
}
.draggable(
state = rememberDraggableState { delta -> currentOffset += delta },
orientation = Orientation.Vertical,
onDragStopped = {
shouldReset = true
currentOffset = 0F
}
)
) {}
}
}
You can define the offset as an Animatable.
While dragging use the method snapTo to update the current value as the initial value and the onDragStopped to start the animation.
val coroutineScope = rememberCoroutineScope()
val offsetY = remember { Animatable(0f) }
Box(contentAlignment = Alignment.TopCenter, modifier = Modifier.fillMaxSize()) {
Surface(
color = Color(0xFF34AB52),
modifier = Modifier
.size(100.dp)
.offset {
IntOffset(0, offsetY.value.roundToInt())
}
.draggable(
state = rememberDraggableState { delta ->
coroutineScope.launch {
offsetY.snapTo(offsetY.value + delta)
}
},
orientation = Orientation.Vertical,
onDragStopped = {
coroutineScope.launch {
offsetY.animateTo(
targetValue = 0f,
animationSpec = tween(
durationMillis = 3000,
delayMillis = 0
)
)
}
}
)
) {
}
}

Scaling Button Animation in Jetpack Compose

I want to build this awesome button animation pressed from the AirBnB App with Jetpack Compose
Unfortunately, the Animation/Transition API was changed recently and there's almost no documentation for it. Can someone help me get the right approach to implement this button press animation?
Edit
Based on #Amirhosein answer I have developed a button that looks almost exactly like the Airbnb example
Code:
#Composable
fun AnimatedButton() {
val boxHeight = animatedFloat(initVal = 50f)
val relBoxWidth = animatedFloat(initVal = 1.0f)
val fontSize = animatedFloat(initVal = 16f)
fun animateDimensions() {
boxHeight.animateTo(45f)
relBoxWidth.animateTo(0.95f)
// fontSize.animateTo(14f)
}
fun reverseAnimation() {
boxHeight.animateTo(50f)
relBoxWidth.animateTo(1.0f)
//fontSize.animateTo(16f)
}
Box(
modifier = Modifier
.height(boxHeight.value.dp)
.fillMaxWidth(fraction = relBoxWidth.value)
.clip(RoundedCornerShape(8.dp))
.background(Color.Black)
.clickable { }
.pressIndicatorGestureFilter(
onStart = {
animateDimensions()
},
onStop = {
reverseAnimation()
},
onCancel = {
reverseAnimation()
}
),
contentAlignment = Alignment.Center
) {
Text(text = "Explore Airbnb", fontSize = fontSize.value.sp, color = Color.White)
}
}
Video:
Unfortunately, I cannot figure out how to animate the text correctly as It looks very bad currently
Are you looking for something like this?
#Composable
fun AnimatedButton() {
val selected = remember { mutableStateOf(false) }
val scale = animateFloatAsState(if (selected.value) 2f else 1f)
Column(
Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = { },
modifier = Modifier
.scale(scale.value)
.height(40.dp)
.width(200.dp)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
selected.value = true }
MotionEvent.ACTION_UP -> {
selected.value = false }
}
true
}
) {
Text(text = "Explore Airbnb", fontSize = 15.sp, color = Color.White)
}
}
}
Here's the implementation I used in my project. Seems most concise to me.
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val sizeScale by animateFloatAsState(if (isPressed) 0.5f else 1f)
Button(
onClick = { },
modifier = Modifier
.wrapContentSize()
.graphicsLayer(
scaleX = sizeScale,
scaleY = sizeScale
),
interactionSource = interactionSource
) { Text(text = "Open the reward") }
Use pressIndicatorGestureFilter to achieve this behavior.
Here is my workaround:
#Preview
#Composable
fun MyFancyButton() {
val boxHeight = animatedFloat(initVal = 60f)
val boxWidth = animatedFloat(initVal = 200f)
Box(modifier = Modifier
.height(boxHeight.value.dp)
.width(boxWidth.value.dp)
.clip(RoundedCornerShape(4.dp))
.background(Color.Black)
.clickable { }
.pressIndicatorGestureFilter(
onStart = {
boxHeight.animateTo(55f)
boxWidth.animateTo(180f)
},
onStop = {
boxHeight.animateTo(60f)
boxWidth.animateTo(200f)
},
onCancel = {
boxHeight.animateTo(60f)
boxWidth.animateTo(200f)
}
), contentAlignment = Alignment.Center) {
Text(text = "Utforska Airbnb", color = Color.White)
}
}
The default jetpack compose Button consumes tap gestures in its onClick event and pressIndicatorGestureFilter doesn't receive taps. That's why I created this custom button
You can use the Modifier.pointerInput to detect the tapGesture.
Define an enum:
enum class ComponentState { Pressed, Released }
Then:
var toState by remember { mutableStateOf(ComponentState.Released) }
val modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = {
toState = ComponentState.Pressed
tryAwaitRelease()
toState = ComponentState.Released
}
)
}
// Defines a transition of `ComponentState`, and updates the transition when the provided [targetState] changes
val transition: Transition<ComponentState> = updateTransition(targetState = toState, label = "")
// Defines a float animation to scale x,y
val scalex: Float by transition.animateFloat(
transitionSpec = { spring(stiffness = 50f) }, label = ""
) { state ->
if (state == ComponentState.Pressed) 1.25f else 1f
}
val scaley: Float by transition.animateFloat(
transitionSpec = { spring(stiffness = 50f) }, label = ""
) { state ->
if (state == ComponentState.Pressed) 1.05f else 1f
}
Apply the modifier and use the Modifier.graphicsLayer to change also the text dimension.
Box(
modifier
.padding(16.dp)
.width((100 * scalex).dp)
.height((50 * scaley).dp)
.background(Color.Black, shape = RoundedCornerShape(8.dp)),
contentAlignment = Alignment.Center) {
Text("BUTTON", color = Color.White,
modifier = Modifier.graphicsLayer{
scaleX = scalex;
scaleY = scaley
})
}
Here is the ScalingButton, the onClick callback is fired when users click the button and state is reset when users move their finger out of the button area after pressing the button and not releasing it. I'm using Modifier.pointerInput function to detect user inputs:
#Composable
fun ScalingButton(onClick: () -> Unit, content: #Composable RowScope.() -> Unit) {
var selected by remember { mutableStateOf(false) }
val scale by animateFloatAsState(if (selected) 0.7f else 1f)
Button(
onClick = onClick,
modifier = Modifier
.scale(scale)
.pointerInput(Unit) {
while (true) {
awaitPointerEventScope {
awaitFirstDown(false)
selected = true
waitForUpOrCancellation()
selected = false
}
}
}
) {
content()
}
}
OR
Another approach without using an infinite loop:
#Composable
fun ScalingButton(onClick: () -> Unit, content: #Composable RowScope.() -> Unit) {
var selected by remember { mutableStateOf(false) }
val scale by animateFloatAsState(if (selected) 0.75f else 1f)
Button(
onClick = onClick,
modifier = Modifier
.scale(scale)
.pointerInput(selected) {
awaitPointerEventScope {
selected = if (selected) {
waitForUpOrCancellation()
false
} else {
awaitFirstDown(false)
true
}
}
}
) {
content()
}
}
If you want to animated button with different types of animation like scaling, rotating and many different kind of animation then you can use this library in jetpack compose. Check Here

Categories

Resources