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)
}
}
}
Related
**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
)
}
}
}
I managed to work this out, and setup 3 cards one on top of the other as seperate boxs compose elements with onclick and on drag properties.
The issue is now, that I'd like the card that I'm pressing/dragging to set to the front, so, I played with the z-index modifier, but, it looks like I'm doing something wrong. Any idea?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Test1Theme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
for (i in 1 until 4) {
DraggableBox(title = "Box_${+1}", initX = 100f*i.toFloat(), initY = 100f, content =
{
Text(text = "Box_${i}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
)
}
}
}
}
}
}
#Composable
fun DraggableBox(title: String, initX: Float = 0f, initY: Float = 0f, content: #Composable() () -> Unit) {
val cardInitWidth = 135f
val cardInitHeight = 190f
val expandValue = 20f
Box(
modifier = Modifier
.fillMaxSize()
) {
val shape = RoundedCornerShape(12.dp)
val coroutineScope = rememberCoroutineScope()
val enable = remember { mutableStateOf(true) }
var offsetX = remember { Animatable(initialValue = initX) }
var offsetY = remember { Animatable(initialValue = initY) }
val interactionSource = remember { MutableInteractionSource() }
val clickable = Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current
) { }
val isPressed by interactionSource.collectIsPressedAsState()
val size = animateSizeAsState(
targetValue = if (enable.value && !isPressed) {
Size(width = cardInitWidth, height = cardInitHeight)
} else {
Size(width = cardInitWidth + expandValue, height = cardInitHeight + expandValue)
}
)
Box(
Modifier
.offset {
IntOffset(
x = offsetX.value.roundToInt(),
y = offsetY.value.roundToInt()
)
}
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
.size(size.value.width.dp, size.value.height.dp)
.clip(shape)
//.background(Color(0xFF5FA777))
.background(color = MaterialTheme.colors.primary)
.border(BorderStroke(2.dp, Color.Black), shape = shape)
.pointerInput(Unit) {
detectDragGestures(
onDragStart = {
enable.value = !enable.value
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
coroutineScope.launch {
offsetX.snapTo(targetValue = offsetX.value + dragAmount.x)
offsetY.snapTo(targetValue = offsetY.value + dragAmount.y)
}
spring(stiffness = Spring.StiffnessHigh, visibilityThreshold = 0.1.dp)
},
onDragEnd = {
enable.value = !enable.value
spring(stiffness = Spring.StiffnessLow, visibilityThreshold = 0.1.dp)
coroutineScope.launch {
launch {
offsetY.animateTo(
targetValue = initY,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
offsetX.animateTo(
targetValue = initX,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
}
)
}
.then(clickable)
) {
Row (modifier = Modifier
.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
)
{
Column (modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "init-X: ${initX.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "init-Y: ${initY.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "offset-X: ${offsetX.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "offset-Y: ${offsetY.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
content()
}
}
}
}
}
}
The Modifier.zIndex works only for children within the same parent.
In your case you should move this modifier to the topmost Box. To do so you have to move enable and isPressed one level up too, and I would move all the other variables as well - but that's just a matter of taste, I guess.
val enable = remember { mutableStateOf(true) }
val isPressed by interactionSource.collectIsPressedAsState()
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
) {
// ...
I am trying to show a section within LazyColumn which has a list of Rows that are shown horizontally using LazyRow. I would like to have a button which displays show/hide so that I can show a minimal list in this section instead of full list. I would like to animate the expand/collapse part and currently expanding on button click is working as expected but when collapsing, the LazyCloumn scrolls up which seems to push this section out of screen (as shown in the video below). Is there any way we can collapse so that the button at least gets snapped to the top and the remaining section is removed? This way, user can still expand the list if required rather than scrolling up to find the button.
I have tried the following but none of them seem to work:
Using AnimatedVisibility
Using animate*AsState low level API's
Also tried to just remove the contents from list allowing LazyColumn to re-order based on the list content
val RandomColor
get() = Color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))
typealias ClickHandler = (Boolean) -> Unit
#Composable
fun DemoLayout(demoDataList: List<DemoData>, isExpanded: Boolean, clickHandler: ClickHandler) {
LazyColumn {
demoDataList.forEachIndexed { index, it ->
when (it) {
is DemoData.Header -> item(key = "cell_$index") { HeaderComposable(header = it) }
is DemoData.BigCard -> item(key = "hero_$index") { BigCardComposable(bigCard = it) }
is DemoData.Card -> item(key = "banner_$index") { CardComposable(card = it) }
is DemoData.ExpandableSection -> {
items(count = 2, key = { indexInner: Int -> "categories_first_half_$index$indexInner" }) { index ->
Section(
sectionInfo = it.sectionInfo[index]
)
}
//Comment below and try another approach
item(key = "first_approach_$index") {
FirstApproach(
expandableSection = DemoData.ExpandableSection(
it.sectionInfo.subList(
3,
5
)
)
)
}
//Second approach
/*if (isExpanded)
items(count = 3, key = { indexInner -> "categories_second_half_$index$indexInner" }) { index ->
Section(
sectionInfo = it.sectionInfo[index + 2]
)
}
item(key = "button_$index") {
ShowHideButton(isExpanded, clickHandler)
}*/
}
}
}
}
}
#Composable
fun FirstApproach(expandableSection: DemoData.ExpandableSection) {
var expanded by remember { mutableStateOf(false) }
val density = LocalDensity.current
Column {
AnimatedVisibility(
visible = expanded,
enter = slideInVertically() +
expandVertically(
// Expand from the top.
expandFrom = Alignment.Top,
animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
) + fadeIn(
// Fade in with the initial alpha of 0.3f.
initialAlpha = 0.3f
),
exit = slideOutVertically(
animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
) + shrinkVertically(
shrinkTowards = Alignment.Bottom,
animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
) + fadeOut(
animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing),
targetAlpha = 0f
)
) {
Column {
for (i in 0 until expandableSection.sectionInfo.size) {
HeaderComposable(header = expandableSection.sectionInfo[i].header)
InfoCardsComposable(expandableSection.sectionInfo[i].infoCard)
DetailsCardComposable(expandableSection.sectionInfo[i].detailCard)
}
}
}
Button(
modifier = Modifier
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
.fillMaxWidth(),
onClick = {
expanded = !expanded
}) {
Text(text = if (expanded) "Hide" else "Show")
}
}
}
#Composable
fun HeaderComposable(header: DemoData.Header) {
Row(
modifier = Modifier
.padding(top = 16.dp)
.fillMaxWidth()
.height(64.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = header.title, modifier = Modifier.padding(horizontal = 16.dp))
}
}
#Composable
fun CardComposable(card: DemoData.Card) {
Card(
modifier = Modifier
.padding(top = 16.dp)
.size(164.dp),
backgroundColor = RandomColor
) {
Text(text = card.cardText, modifier = Modifier.padding(horizontal = 16.dp))
}
}
#Composable
fun BigCardComposable(bigCard: DemoData.BigCard) {
Card(
modifier = Modifier
.padding(top = 16.dp)
.size(172.dp),
backgroundColor = RandomColor
) {
Text(text = bigCard.bigCardText, modifier = Modifier.padding(horizontal = 16.dp))
}
}
#Composable
fun Section(sectionInfo: SectionInfo) {
Column(
modifier = Modifier.animateContentSize()
) {
HeaderComposable(header = sectionInfo.header)
InfoCardsComposable(sectionInfo.infoCard)
DetailsCardComposable(sectionInfo.detailCard)
}
}
#Composable
private fun ShowHideButton(isExpanded: Boolean, clickHandler: ClickHandler) {
Button(
modifier = Modifier
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
.fillMaxWidth(),
onClick = {
clickHandler.invoke(
!isExpanded
)
}) {
Text(text = if (isExpanded) "Hide" else "Show")
}
}
#Composable
fun DetailsCardComposable(detailCardsList: List<DetailCard>) {
LazyRow(
modifier = Modifier.padding(top = 16.dp)
) {
items(detailCardsList) {
DetailCardComposable(detailCard = it)
}
}
}
#Composable
fun InfoCardsComposable(infoCardsList: List<InfoCard>) {
LazyRow(
modifier = Modifier.padding(top = 16.dp)
) {
items(infoCardsList) {
InfoCardComposable(infoCard = it)
}
}
}
#Composable
fun InfoCardComposable(infoCard: InfoCard) {
Card(
modifier = Modifier
.size(136.dp),
backgroundColor = RandomColor
) {
Text(text = infoCard.infoText, modifier = Modifier.padding(horizontal = 16.dp))
}
}
#Composable
fun DetailCardComposable(detailCard: DetailCard) {
Card(
modifier = Modifier
.size(156.dp),
backgroundColor = RandomColor
) {
Text(text = detailCard.detailText, modifier = Modifier.padding(horizontal = 16.dp))
}
}
Complete code to try out is available here: https://github.com/DirajHS/ComposeAnimation/tree/master
I would like to know if this is the expected behavior or am I doing something wrong?
Any suggestions on snapping the button to the top during collapse would be much appreciated.
I have a page like this:
When one box is focused, it will be scaled. I use Modifier.graphicsLayer() to scale it.
but the scaled box will be covered by other boxes(box01 is covered by box02,box04 and box 05)
what I actually need is: the scaled box covers other boxes,like this:
My Sample Code:
#Composable
fun FocusBox(
title:String,
requester: FocusRequester = FocusRequester(),
modifier: Modifier = Modifier
) {
var boxColor by remember { mutableStateOf(Color.White) }
var scale by remember { mutableStateOf(1f) }
Box(
Modifier
.focusRequester(requester)
.onFocusChanged {
boxColor = if (it.isFocused) Color.Green else Color.Gray
scale = if (it.isFocused) { 1.3f } else { 1f }
}
.focusable()
.graphicsLayer(
scaleX = scale,
scaleY = scale
).background(boxColor)
) {
Text(
text = title,
modifier = Modifier.padding(30.dp),
color = Color.White,
style = MaterialTheme.typography.subtitle2
)
}
}
#Composable
fun FocusScaleBoxDemo(){
Row(modifier = Modifier.padding(30.dp)){
Column{
FocusBox(title = "Box_01")
Spacer(modifier = Modifier.padding(5.dp))
FocusBox(title = "Box_02")
Spacer(modifier = Modifier.padding(5.dp))
FocusBox(title = "Box_03")
Spacer(modifier = Modifier.padding(5.dp))
}
Spacer(modifier = Modifier.padding(5.dp))
Column{
FocusBox(title = "Box_04")
Spacer(modifier = Modifier.padding(5.dp))
FocusBox(title = "Box_05")
Spacer(modifier = Modifier.padding(5.dp))
FocusBox(title = "Box_06")
Spacer(modifier = Modifier.padding(5.dp))
}
}
}
Basically you need zIndex to bring view under neighbours. But this modifier only works for one container. So if you only add it to the selected box, neighbour column will still be on top of that. You need to add it to the Column containing selected box too.
I also prettified you code a little bit: try to avoid code repetition as much as possible - you'll decrease mistake chances and increase modifications speed
#Composable
fun FocusScaleBoxDemo() {
val columnsCount = 2
val rowsCount = 3
var focusedColumnIndex by remember { mutableStateOf(0) }
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.padding(30.dp)
) {
for (column in 0 until columnsCount) {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.zIndex(if (column == focusedColumnIndex) 1f else 0f)
) {
for (row in 0 until rowsCount) {
val boxIndex = column * rowsCount + row
FocusBox(
title = "Box_${boxIndex + 1}",
onFocused = {
focusedColumnIndex = column
},
)
}
}
}
}
}
#Composable
fun FocusBox(
title: String,
onFocused: () -> Unit,
requester: FocusRequester = remember { FocusRequester() },
) {
var isFocused by remember { mutableStateOf(false) }
val scale = if (isFocused) 1.3f else 1f
Box(
Modifier
.focusRequester(requester)
.onFocusChanged {
isFocused = it.isFocused
if (isFocused) {
onFocused()
}
}
.focusable()
.graphicsLayer(
scaleX = scale,
scaleY = scale
)
.background(if (isFocused) Color.Green else Color.Gray)
.zIndex(if (isFocused) 1f else 0f)
) {
Text(
text = title,
modifier = Modifier.padding(30.dp),
color = Color.White,
style = MaterialTheme.typography.subtitle2
)
}
}
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