Created
May 20, 2025 12:56
-
-
Save robertlevonyan/651fde33267837e7378f8e199d2c9cb8 to your computer and use it in GitHub Desktop.
Expressive Button Animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun ExpressiveButtonAnimation() { | |
var selectedIndex by remember { mutableIntStateOf(-1) } | |
val checkedColor = Color(0xFF554F6E) | |
val uncheckedColor = Color(0xFFEAE5FF) | |
val icons = listOf(Icons.Outlined.Alarm, Icons.Outlined.LinkOff, Icons.Outlined.Wifi) | |
var weights = remember { mutableStateListOf(0.85f, 0.65f, 1.5f) } | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.background(Color(0xFFDAD2FF)), | |
contentAlignment = Alignment.Center, | |
) { | |
Row( | |
modifier = Modifier | |
.fillMaxWidth(0.6f) | |
.padding(horizontal = 8.dp) | |
) { | |
icons.forEachIndexed { index, icon -> | |
ExpressiveFloatingActionButton( | |
icon = icon, | |
itemWeight = weights[index], | |
checked = selectedIndex == index, | |
checkedColor = checkedColor, | |
uncheckedColor = uncheckedColor, | |
onClick = { | |
selectedIndex = if (index == selectedIndex) { | |
-1 | |
} else { | |
index | |
} | |
} | |
) | |
} | |
} | |
} | |
} | |
@Composable | |
fun RowScope.ExpressiveFloatingActionButton( | |
icon: ImageVector, | |
itemWeight: Float, | |
checked: Boolean, | |
checkedColor: Color, | |
uncheckedColor: Color, | |
onClick: () -> Unit, | |
) { | |
var shapeSelected by remember { mutableStateOf(false) } | |
val animatedRadius by animateDpAsState( | |
targetValue = if (shapeSelected) 6.dp else 16.dp, | |
label = "animatedRadius" | |
) | |
val animatedWeight by animateFloatAsState( | |
targetValue = if (shapeSelected) 0.25f else 0f | |
) | |
val iconSize = 70.dp | |
IconButton( | |
modifier = Modifier | |
.padding(4.dp) | |
.weight(itemWeight + animatedWeight) | |
.height(iconSize) | |
.pointerInput(Unit) { | |
awaitPointerEventScope { | |
while (true) { | |
val event = awaitPointerEvent(PointerEventPass.Main) | |
event.changes.forEach { pointerInputChange -> | |
shapeSelected = pointerInputChange.pressed | |
} | |
} | |
} | |
}, | |
colors = IconButtonDefaults.filledIconButtonColors( | |
containerColor = if (shapeSelected || checked) checkedColor else uncheckedColor, | |
contentColor = if (shapeSelected || checked) uncheckedColor else checkedColor, | |
), | |
shape = MaterialTheme.shapes.large.copy(CornerSize(animatedRadius)), | |
onClick = onClick, | |
) { | |
Icon( | |
imageVector = icon, | |
contentDescription = null, | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment