Jetpack Compose: āđāļāļāļīāđāļĄāļāļąāļāļāļąāđāļāļŠāļđāļāļāļĩāļĨāļ°āļāļąāđāļāļāļāļ
āļāļđāđāļĄāļ·āļāļāļāļąāļāļŠāļĄāļāļđāļĢāļāđāđāļāļĩāđāļĒāļ§āļāļąāļāđāļāļāļīāđāļĄāļāļąāļāļāļąāđāļāļŠāļđāļāđāļ Compose: āļāļĢāļēāļāļāļīāļāļąāļ AnimatedVisibility, Animatable, āļāđāļēāļāļēāļ āđāļĨāļ°āļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāļŠāļģāļŦāļĢāļąāļāļāļīāļāđāļāļāļĢāđāđāļāļ Android āļāļĩāđāļĨāļ·āđāļāđāļŦāļĨ

āđāļāļāļīāđāļĄāļāļąāļāđāļāļĨāļĩāđāļĒāļāđāļāļāļāļĨāļīāđāļāļāļąāļāđāļāļīāļāļāļąāļāļāđāļāļąāļāđāļŦāđāļāļĨāļēāļĒāđāļāđāļāļāļĢāļ°āļŠāļāļāļēāļĢāļāđāļāļđāđāđāļāđāļāļĩāđāļāđāļēāļāļāļāļģ Jetpack Compose āļĄāļĩ API āđāļāļāļīāđāļĄāļāļąāļāđāļāļāļāļĢāļ°āļāļēāļĻāļāļĩāđāļāļĢāļāļāļĨāļąāļāļāļķāđāļāļāđāļ§āļĒāđāļŦāđāļāļēāļĢāļŠāļĢāđāļēāļāļāļīāļāđāļāļāļĢāđāđāļāļāļāļĩāđāļĨāļ·āđāļāđāļŦāļĨāļāđāļēāļĒāļāļķāđāļāļāļĒāđāļēāļāļĄāļēāļ āļāļđāđāļĄāļ·āļāļāļĩāđāļŠāļģāļĢāļ§āļāđāļāļāļāļīāļāļāļąāđāļāļŠāļđāļāđāļāļāļēāļĢāļŠāļĢāđāļēāļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļĄāļĩāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāđāļĨāļ°āļāļģāļĢāļļāļāļĢāļąāļāļĐāļēāļāđāļēāļĒ
āļāļāđāļāļ°āļāļģāļāļĩāđāļŠāļąāļāļāļīāļĐāļāļēāļāļ§āđāļēāļāļđāđāļāđāļēāļāļāļļāđāļāđāļāļĒāļāļąāļāļāļ·āđāļāļāļēāļāļāļāļ Compose (recomposition, state, modifier) āļŠāļģāļŦāļĢāļąāļāļāļ·āđāļāļāļēāļāļāļ§āļĢāļāđāļēāļāļāļđāđāļĄāļ·āļāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ Jetpack Compose āļāđāļāļ
āļāļ·āđāļāļāļēāļāļāļāļ API āđāļāļāļīāđāļĄāļāļąāļāđāļ Compose
Compose āļĄāļĩ API āļŦāļĨāļēāļĒāļĢāļ°āļāļąāļāļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļ āļāļēāļĢāđāļĨāļ·āļāļāļāļķāđāļāļāļĒāļđāđāļāļąāļāļĢāļ°āļāļąāļāļāļēāļĢāļāļ§āļāļāļļāļĄāļāļĩāđāļāđāļāļāļāļēāļĢāđāļĨāļ°āļāļ§āļēāļĄāļāļąāļāļāđāļāļāļāļāļāđāļāļāļīāđāļĄāļāļąāļ
API āđāļāđāļāļāļāļāđāļāđāļāļŠāļēāļĄāļāļĢāļ°āđāļ āļāļŦāļĨāļąāļ āđāļāđāđāļāđ āđāļāļāļīāđāļĄāļāļąāļāļĢāļ°āļāļąāļāļŠāļđāļ (AnimatedVisibility, AnimatedContent) āđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļāļīāļāļāļēāļĄāļŠāļāļēāļāļ° (animate*AsState) āđāļĨāļ°āđāļāļāļīāđāļĄāļāļąāļāļĢāļ°āļāļąāļāļāđāļģ (Animatable, Transition)
// Overview of the three animation API levels
@Composable
fun AnimationApiOverview() {
// High level: simple predefined animations
AnimatedVisibility(visible = isVisible) {
Text("Animated content")
}
// Intermediate level: state-driven animation
val alpha by animateFloatAsState(
targetValue = if (isSelected) 1f else 0.5f,
label = "alpha"
)
// Low level: full control over animation
val animatable = remember { Animatable(0f) }
LaunchedEffect(targetValue) {
animatable.animateTo(targetValue)
}
}āļāļēāļĢāđāļĨāļ·āļāļāļĢāļ°āļāļąāļ API āļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļĄāđāļāđāļāļŠāļīāđāļāļŠāļģāļāļąāļāđāļāļ·āđāļāļĢāļąāļāļĐāļēāđāļāđāļāđāļŦāđāļāđāļēāļāļāđāļēāļĒāļāļāļ°āļāļĩāđāļĒāļąāļāļāļāļāļ§āļēāļĄāļĒāļ·āļāļŦāļĒāļļāđāļāļāļĩāđāļāļģāđāļāđāļ
AnimatedVisibility: āđāļāļāļīāđāļĄāļāļąāļāļāļēāļĢāđāļāđāļēāđāļĨāļ°āļāļāļāļāļĩāđāļŦāļĢāļđāļŦāļĢāļē
AnimatedVisibility āđāļāđāļāļāļļāļāđāļĢāļīāđāļĄāļāđāļāļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāđāļāļāļīāđāļĄāļāļāļēāļĢāļāļĢāļēāļāļāđāļĨāļ°āļŦāļēāļĒāđāļāļāļāļāļāļīāļĨāļīāđāļĄāļāļāđ API āļāļĩāđāļāļąāļāļāļēāļĢāļāļēāļĢāļāļāļĄāđāļāļŠāđāļĨāļ°āļāļĩāļāļāļĄāđāļāļŠāđāļāļ·āđāļāļŦāļēāđāļāļĒāļāļąāļāđāļāļĄāļąāļāļī
āļāļēāļĢāļēāļĄāļīāđāļāļāļĢāđ enter āđāļĨāļ° exit āļĢāļąāļāļāļēāļĢāļĢāļ§āļĄāļāļąāļāļāļāļāļāļĢāļēāļāļāļīāļāļąāļāļāļĩāđāļāļģāļŦāļāļāļāļĪāļāļīāļāļĢāļĢāļĄāļāļāļāđāļāļāļīāđāļĄāļāļąāļ āļāļĢāļēāļāļāļīāļāļąāļāđāļŦāļĨāđāļēāļāļĩāđāļŠāļēāļĄāļēāļĢāļāļĢāļ§āļĄāļāļąāļāļāđāļ§āļĒāļāļąāļ§āļāļģāđāļāļīāļāļāļēāļĢ +
@Composable
fun ExpandableCard(
title: String,
content: String,
modifier: Modifier = Modifier
) {
var isExpanded by remember { mutableStateOf(false) }
Card(
modifier = modifier.clickable { isExpanded = !isExpanded }
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Icon(
imageVector = if (isExpanded) Icons.Default.ExpandLess
else Icons.Default.ExpandMore,
contentDescription = null
)
}
// Expansion animation with fade + slide
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn(animationSpec = tween(300)) +
expandVertically(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200)) +
shrinkVertically(animationSpec = tween(200))
) {
Text(
text = content,
modifier = Modifier.padding(top = 12.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}āđāļāļ·āđāļāļŦāļēāļ āļēāļĒāđāļ AnimatedVisibility āļāļ°āļāļđāļāļāļāļĄāđāļāļŠāđāļāļāļēāļ°āđāļĄāļ·āđāļ visible = true āļāļķāđāļāļāđāļ§āļĒāļāļĢāļąāļāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāļŠāļģāļŦāļĢāļąāļāļĢāļēāļĒāļāļēāļĢāļāļĩāđāļĄāļĩāļāļīāļĨāļīāđāļĄāļāļāđāļāļĩāđāļāļĒāļēāļĒāđāļāđāļāļģāļāļ§āļāļĄāļēāļ
āļāļĢāļēāļāļāļīāļāļąāļāļāļĩāđāđāļāđāđāļāđ āđāļāđāđāļāđ fadeIn/fadeOut, slideIn/slideOut, expandIn/shrinkOut, scaleIn/scaleOut āļŠāļēāļĄāļēāļĢāļāļĢāļ§āļĄāļāļąāļāđāļāđāļāļĒāđāļēāļāļāļīāļŠāļĢāļ°āđāļāļ·āđāļāļŠāļĢāđāļēāļāđāļāļāđāļāļāļāđāļāļĩāđāļāļģāļŦāļāļāđāļāļ
animate*AsState: āđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļāļąāļāđāļāļĨāļ·āđāļāļāļāđāļ§āļĒāļŠāļāļēāļāļ°
āļāļĨāļļāđāļĄāļāļąāļāļāđāļāļąāļ animate*AsState āđāļāļāļīāđāļĄāļāļāļēāļĢāđāļāļĨāļĩāđāļĒāļāđāļāļĨāļāļāļāļāļāđāļēāļāļ·āđāļāļāļēāļāđāļāļĒāļāļąāļāđāļāļĄāļąāļāļī āļāļĩāđāļāļ·āļāđāļāļ§āļāļēāļāļāļĩāđāđāļāđāļāđāļāļāļĨāļąāļāļĐāļāđāļāļĩāđāļŠāļļāļāļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļāļāļĢāļĢāļĄāļāļēāđāļ Compose
āļāđāļāļĄāļđāļĨāđāļāđāļĨāļ°āļāļĢāļ°āđāļ āļāļĄāļĩāļāļąāļāļāđāļāļąāļāđāļāļāļēāļ°āļāļāļāļāļ: animateColorAsState, animateFloatAsState, animateDpAsState, animateIntAsState āđāļāđāļāļāđāļ
@Composable
fun InteractiveButton(
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
// Animated background color
val backgroundColor by animateColorAsState(
targetValue = if (isSelected) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.surfaceVariant,
animationSpec = tween(durationMillis = 250),
label = "backgroundColor"
)
// Animated elevation
val elevation by animateDpAsState(
targetValue = if (isSelected) 8.dp else 2.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "elevation"
)
// Animated text size
val textSize by animateFloatAsState(
targetValue = if (isSelected) 18f else 14f,
label = "textSize"
)
Surface(
modifier = modifier.clickable(onClick = onClick),
color = backgroundColor,
shadowElevation = elevation,
shape = RoundedCornerShape(12.dp)
) {
Text(
text = if (isSelected) "Selected" else "Select",
modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp),
fontSize = textSize.sp
)
}
}āļāļēāļĢāļēāļĄāļīāđāļāļāļĢāđ animationSpec āļāļ§āļāļāļļāļĄāļāļĪāļāļīāļāļĢāļĢāļĄāđāļāļīāļāđāļ§āļĨāļēāļāļāļāđāļāļāļīāđāļĄāļāļąāļ Spec āļāļĩāđāđāļāđāļāđāļāļĒāļāļĩāđāļŠāļļāļāļŠāļāļāļĢāļēāļĒāļāļēāļĢāļāļ·āļ tween (āļĢāļ°āļĒāļ°āđāļ§āļĨāļēāļāļāļāļĩāđāļāļĢāđāļāļĄ easing) āđāļĨāļ° spring (āļāļīāļŠāļīāļāļŠāđāļāļĩāđāļŠāļĄāļāļĢāļīāļāļāļĢāđāļāļĄāļāļēāļĢāđāļāđāļ)
Transition: āļāļēāļĢāļāļĢāļ°āļŠāļēāļāļāļēāļāļŦāļĨāļēāļĒāđāļāļāļīāđāļĄāļāļąāļ
āđāļĄāļ·āđāļāļāđāļāļāđāļāļāļīāđāļĄāļāļāļļāļāļŠāļĄāļāļąāļāļīāļŦāļĨāļēāļĒāļāļĒāđāļēāļāđāļŦāđāļāļĢāļ°āļŠāļēāļāļāļąāļ updateTransition āđāļŦāđāļāļēāļĢāļāļ§āļāļāļļāļĄāđāļāļāļĢāļ§āļĄāļĻāļđāļāļĒāđ API āļāļĩāđāļĢāļąāļāļāļĢāļ°āļāļąāļāļ§āđāļēāđāļāļāļīāđāļĄāļāļąāļāļāļąāđāļāļŦāļĄāļāļāļ°āļāļāļāļēāļĢāļāļĢāļ°āļŠāļēāļāđāļ§āļĨāļēāđāļ§āđ
āļĢāļđāļāđāļāļāļāļĩāđāļāļĢāļ°āļāļāļāļāđāļ§āļĒāļāļēāļĢāļāļģāļŦāļāļāļŠāļāļēāļāļ°āđāļāļ enum āļāļēāļāļāļąāđāļāļŠāļĢāđāļēāļāđāļāļāļīāđāļĄāļāļąāļāļŠāļģāļŦāļĢāļąāļāļāļļāļāļŠāļĄāļāļąāļāļīāđāļāđāļĨāļ°āļāļĒāđāļēāļāļāļĩāđāļāļķāđāļāļāļĒāļđāđāļāļąāļāļŠāļāļēāļāļ°āļāļąāđāļ
// Card state: defines visual behavior
enum class CardState { Collapsed, Expanded, Selected }
@Composable
fun AnimatedStateCard(
cardState: CardState,
modifier: Modifier = Modifier
) {
// Central transition coordinating all animations
val transition = updateTransition(
targetState = cardState,
label = "cardTransition"
)
// Card height based on state
val cardHeight by transition.animateDp(
transitionSpec = { spring(stiffness = Spring.StiffnessLow) },
label = "height"
) { state ->
when (state) {
CardState.Collapsed -> 80.dp
CardState.Expanded -> 200.dp
CardState.Selected -> 160.dp
}
}
// Border color based on state
val borderColor by transition.animateColor(
transitionSpec = { tween(300) },
label = "borderColor"
) { state ->
when (state) {
CardState.Collapsed -> Color.Transparent
CardState.Expanded -> MaterialTheme.colorScheme.outline
CardState.Selected -> MaterialTheme.colorScheme.primary
}
}
// Corner radius based on state
val cornerRadius by transition.animateDp(
label = "cornerRadius"
) { state ->
when (state) {
CardState.Collapsed -> 8.dp
CardState.Expanded -> 16.dp
CardState.Selected -> 24.dp
}
}
Card(
modifier = modifier
.height(cardHeight)
.border(2.dp, borderColor, RoundedCornerShape(cornerRadius)),
shape = RoundedCornerShape(cornerRadius)
) {
// Card content
}
}āļāļĢāđāļāļĄāļāļĩāđāļāļ°āļāļīāļāļīāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ Android āđāļĨāđāļ§āļŦāļĢāļ·āļāļĒāļąāļāļāļĢāļąāļ?
āļāļķāļāļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāđāļāļāđāļāđāļāļāļ, flashcards āđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
Animatable: āļāļēāļĢāļāļ§āļāļāļļāļĄāđāļāļāļīāđāļĄāļāļąāļāđāļāđāļĄāļĢāļđāļāđāļāļ
Animatable āđāļāđāļ API āļĢāļ°āļāļąāļāļāđāļģāļāļĩāđāđāļŦāđāļāļēāļĢāļāļ§āļāļāļļāļĄāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāļāļĒāđāļēāļāļŠāļĄāļāļđāļĢāļāđ āđāļāļ§āļāļēāļāļāļĩāđāļāļģāđāļāđāļāļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļŠāļēāļĄāļēāļĢāļāļāļđāļāļāļąāļāļāļąāļāļŦāļ§āļ°āđāļāđ āļāđāļēāļāļēāļ āļŦāļĢāļ·āļāļŠāļāļēāļāļāļēāļĢāļāđāļāļĩāđāļāļąāļāļāđāļāļ
āļāđāļēāļāļāļēāļ animate*AsState, Animatable āļāđāļ§āļĒāđāļŦāđāļŠāļēāļĄāļēāļĢāļāļŦāļĒāļļāļ āļĒāđāļāļāļāļĨāļąāļ āļŦāļĢāļ·āļāļāļĢāļąāļāđāļāļĨāļĩāđāļĒāļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļāļģāļĨāļąāļāļāļģāđāļāļīāļāļāļēāļĢāđāļāđāđāļāļĒāđāļĄāđāļāđāļāļāļĢāļāđāļŦāđāđāļŠāļĢāđāļāļŠāļīāđāļ
@Composable
fun SwipeableCard(
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
// Horizontal offset controlled by Animatable
val offsetX = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
// Swipe threshold to trigger dismissal
val dismissThreshold = 300f
Box(
modifier = modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.pointerInput(Unit) {
detectHorizontalDragGestures(
onDragEnd = {
scope.launch {
if (abs(offsetX.value) > dismissThreshold) {
// Animate out then callback
val target = if (offsetX.value > 0) 1000f else -1000f
offsetX.animateTo(
targetValue = target,
animationSpec = tween(200)
)
onDismiss()
} else {
// Return to initial position with spring
offsetX.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy
)
)
}
}
},
onHorizontalDrag = { _, dragAmount ->
scope.launch {
// snapTo for instant finger tracking
offsetX.snapTo(offsetX.value + dragAmount)
}
}
)
}
) {
content()
}
}āđāļĄāļāļāļāļŠāļģāļāļąāļāļāļāļ Animatable āļāļ·āļ animateTo() (āđāļāļāļīāđāļĄāļāđāļāļĒāļąāļāđāļāđāļēāļŦāļĄāļēāļĒ) snapTo() (āđāļāļĨāļĩāđāļĒāļāļāļąāļāļāļĩ) āđāļĨāļ° stop() (āļāļąāļāļāļąāļāļŦāļ§āļ°)
AnimatedContent: āļāļĢāļēāļāļāļīāļāļąāļāļāļāļāđāļāļ·āđāļāļŦāļē
AnimatedContent āđāļāļāļīāđāļĄāļāļāļĢāļēāļāļāļīāļāļąāļāļĢāļ°āļŦāļ§āđāļēāļāđāļāļ·āđāļāļŦāļēāļāļĩāđāđāļāļāļāđāļēāļāļāļąāļ API āļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāđāļāļĨāļĩāđāļĒāļāđāļāļĨāļāļŠāļāļēāļāļ°āļāļĩāđāļāļĢāļąāļāđāļāļĨāļĩāđāļĒāļ UI āļāļĩāđāđāļŠāļāļāđāļāļĒāļŠāļīāđāļāđāļāļīāļ
āļāļĩāļĒāđ targetState āļāļģāļŦāļāļāļ§āđāļēāļāļĢāļēāļāļāļīāļāļąāļāļāļ§āļĢāđāļāļīāļāļāļķāđāļāđāļĄāļ·āđāļāđāļ transitionSpec āļāļģāļŦāļāļāļ§āđāļēāđāļāļ·āđāļāļŦāļēāļāļĩāđāļāļāļāđāļĨāļ°āđāļāļ·āđāļāļŦāļēāļāļĩāđāđāļāđāļēāļāļ°āļĄāļĩāļāļāļīāļŠāļąāļĄāļāļąāļāļāđāļāļąāļāļāļĒāđāļēāļāđāļĢ
@Composable
fun CounterWithAnimation(
count: Int,
modifier: Modifier = Modifier
) {
AnimatedContent(
targetState = count,
modifier = modifier,
transitionSpec = {
// Determine animation direction
val direction = if (targetState > initialState) {
// New number enters from top
slideInVertically { height -> -height } + fadeIn() togetherWith
slideOutVertically { height -> height } + fadeOut()
} else {
// New number enters from bottom
slideInVertically { height -> height } + fadeIn() togetherWith
slideOutVertically { height -> -height } + fadeOut()
}
direction.using(SizeTransform(clip = false))
},
label = "counter"
) { targetCount ->
Text(
text = "$targetCount",
style = MaterialTheme.typography.displayLarge,
fontWeight = FontWeight.Bold
)
}
}āđāļāļ·āđāļāļŦāļēāļ āļēāļĒāđāļ AnimatedContent āļāļ°āļāļđāļ recompose āļāļļāļāļāļĢāļąāđāļāļāļĩāđ targetState āđāļāļĨāļĩāđāļĒāļ āļŠāļģāļŦāļĢāļąāļāđāļāļ·āđāļāļŦāļēāļāļĩāđāļāļąāļāļāđāļāļ āļāļ§āļĢāđāļāđāļāļāļīāļĨāļīāđāļĄāļāļāđāļāļĩāđāļĄāļĩāļāđāļāļāļļāļāļŠāļđāļāđāļ§āđāđāļāđāļāļāļŦāļĢāļ·āļāđāļāđāļāļĨāļĒāļļāļāļāđāļāļĩāļĒāđāļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļĄ
āđāļāļāļīāđāļĄāļāļąāļāđāļĄāđāļāļģāļāļąāļāļāđāļ§āļĒ rememberInfiniteTransition
āļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļ§āļāļāđāļģ (āļāļąāļ§āļāđāļāļāļĩāđāļāļēāļĢāđāļŦāļĨāļ āđāļāļāđāļāļāļāđāļāļēāļĢāđāļāđāļ) rememberInfiniteTransition āļĄāļĩ API āđāļāļāļēāļ°āļāļĩāđāđāļĄāđāļāđāļāļāļāļąāļāļāļēāļĢāļĢāļāļāļāđāļ§āļĒāļāļāđāļāļ
@Composable
fun PulsingDot(
color: Color = MaterialTheme.colorScheme.primary,
modifier: Modifier = Modifier
) {
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
// Looping scale animation
val scale by infiniteTransition.animateFloat(
initialValue = 0.8f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(600, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
// Synchronized opacity animation
val alpha by infiniteTransition.animateFloat(
initialValue = 0.5f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(600, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
Box(
modifier = modifier
.size(24.dp)
.scale(scale)
.alpha(alpha)
.background(color = color, shape = CircleShape)
)
}āđāļāļāļīāđāļĄāļāļąāļāļĢāļēāļĒāļāļēāļĢāļāđāļ§āļĒ LazyColumn
āđāļāļāļīāđāļĄāļāļąāļāļŠāļģāļŦāļĢāļąāļāļĢāļēāļĒāļāļēāļĢāļāđāļāļāđāļŦāđāļāļ§āļēāļĄāļŠāļģāļāļąāļāđāļāđāļāļāļīāđāļĻāļĐ Modifier animateItem() (āđāļāļīāļĄāļāļ·āļ animateItemPlacement) āđāļāļāļīāđāļĄāļāļāļēāļĢāļāļąāļāđāļĢāļĩāļĒāļāđāļŦāļĄāđāđāļāļĒāļāļąāļāđāļāļĄāļąāļāļī
@Composable
fun AnimatedTaskList(
tasks: List<Task>,
onToggle: (Task) -> Unit,
onDelete: (Task) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = tasks,
key = { it.id } // Stable key required for animateItem
) { task ->
var isVisible by remember { mutableStateOf(true) }
// Exit animation before deletion
AnimatedVisibility(
visible = isVisible,
exit = shrinkVertically() + fadeOut()
) {
TaskItem(
task = task,
onToggle = { onToggle(task) },
onDelete = {
isVisible = false
// Delay to let animation complete
},
modifier = Modifier.animateItem(
fadeInSpec = tween(300),
fadeOutSpec = tween(300),
placementSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
}
// Trigger deletion after animation
LaunchedEffect(isVisible) {
if (!isVisible) {
delay(300)
onDelete(task)
}
}
}
}
}āļŦāļēāļāđāļĄāđāļĄāļĩāļāļēāļĢāļēāļĄāļīāđāļāļāļĢāđ key āļāļĩāđāđāļŠāļāļĩāļĒāļĢ animateItem āļāļ°āđāļĄāđāļŠāļēāļĄāļēāļĢāļāļāļīāļāļāļēāļĄāļāļīāļĨāļīāđāļĄāļāļāđāļĢāļ°āļŦāļ§āđāļēāļāļāļēāļĢ recompose āđāļāđ āļāļ§āļĢāđāļāđ ID āļāļĩāđāđāļĄāđāļāđāļģāđāļāļāļāļąāļāļāļĩāļāļāļāļĢāļēāļĒāļāļēāļĢ
āđāļāļāļīāđāļĄāļāļąāļ Canvas
āļŠāļģāļŦāļĢāļąāļāđāļāļāđāļāļāļāđāļ āļēāļāļāļĩāđāļāļģāļŦāļāļāđāļāļ āļāļēāļĢāļĢāļ§āļĄ Canvas āļāļąāļāļāđāļēāļāļĩāđāđāļāļāļīāđāļĄāļāđāļŦāđāļāļ§āļēāļĄāļĒāļ·āļāļŦāļĒāļļāđāļāļāļąāđāļāļŦāļĄāļ
@Composable
fun AnimatedProgressRing(
progress: Float, // 0f to 1f
modifier: Modifier = Modifier
) {
// Progress animation with spring for natural feel
val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessVeryLow
),
label = "progress"
)
// Continuous rotation animation
val infiniteTransition = rememberInfiniteTransition(label = "rotation")
val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = LinearEasing)
),
label = "rotation"
)
val primaryColor = MaterialTheme.colorScheme.primary
val trackColor = MaterialTheme.colorScheme.surfaceVariant
Canvas(
modifier = modifier
.size(120.dp)
.rotate(rotation)
) {
val strokeWidth = 12.dp.toPx()
val radius = (size.minDimension - strokeWidth) / 2
// Background circle (track)
drawCircle(
color = trackColor,
radius = radius,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
)
// Animated progress arc
drawArc(
color = primaryColor,
startAngle = -90f,
sweepAngle = animatedProgress * 360f,
useCenter = false,
style = Stroke(width = strokeWidth, cap = StrokeCap.Round),
topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
size = Size(radius * 2, radius * 2)
)
}
}āļāļĢāđāļāļĄāļāļĩāđāļāļ°āļāļīāļāļīāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ Android āđāļĨāđāļ§āļŦāļĢāļ·āļāļĒāļąāļāļāļĢāļąāļ?
āļāļķāļāļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāđāļāļāđāļāđāļāļāļ, flashcards āđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āļāļēāļĢāđāļāļīāđāļĄāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāđāļāļāļīāđāļĄāļāļąāļ
āđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāļāļĢāļąāļāđāļāđāļāđāļĄāđāļāļĩāļāļēāļāļāļģāđāļŦāđāđāļāļīāļāļāļēāļāļēāļĢāļāļĢāļ°āļāļļāļ (jank) āđāļĨāļ°āļāļģāđāļŦāđāđāļāļāđāļāļāļĢāļĩāđāļŦāļĄāļāđāļĢāđāļ§ āļāļĩāđāļāļ·āļāđāļāļ§āļāļēāļāļāļāļīāļāļąāļāļīāļāļĩāđāļāļĩāļāļĩāđāļŠāļļāļāđāļāļāļēāļĢāļĢāļąāļāļĐāļē 60 FPS
āļāļāļāđāļāđāļĢāļāļāļ·āļāļŦāļĨāļĩāļāđāļĨāļĩāđāļĒāļāļāļēāļĢāļāļąāļāļŠāļĢāļĢāļŦāļāđāļ§āļĒāļāļ§āļēāļĄāļāļģāļĢāļ°āļŦāļ§āđāļēāļāđāļāļāļīāđāļĄāļāļąāļ āļāļ§āļĢāđāļāđ graphicsLayer āđāļāļ modifier āļāļĩāđāļāļĢāļ°āļāļļāđāļāļāļēāļĢ recompose
@Composable
fun OptimizedAnimatedCard(
isExpanded: Boolean,
modifier: Modifier = Modifier
) {
val scale by animateFloatAsState(
targetValue = if (isExpanded) 1.1f else 1f,
label = "scale"
)
val alpha by animateFloatAsState(
targetValue = if (isExpanded) 1f else 0.8f,
label = "alpha"
)
Card(
modifier = modifier
// â
graphicsLayer: GPU modifications without recomposition
.graphicsLayer {
scaleX = scale
scaleY = scale
this.alpha = alpha
}
// â Avoid: .scale(scale).alpha(alpha)
// These modifiers trigger recompositions
) {
Text("Card content")
}
}
// Example with remembered lambda to avoid allocations
@Composable
fun OptimizedClickableItem(
onClick: () -> Unit,
content: @Composable () -> Unit
) {
// â
Stable remembered lambda
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = ripple(),
onClick = onClick
)
) {
content()
}
}āļāļļāļāļŠāļģāļāļąāļāļĨāļģāļāļąāļāļāļĩāđāļŠāļāļāđāļāļĩāđāļĒāļ§āļāđāļāļāļāļąāļāđāļāļāļīāđāļĄāļāļąāļāđāļāļĢāļēāļĒāļāļēāļĢ āļāļ§āļĢāļāļģāļāļąāļāļāļģāļāļ§āļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāđāļāļīāļāļāļķāđāļāļāļĢāđāļāļĄāļāļąāļāđāļĨāļ°āđāļāđ derivedStateOf āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļģāļāļ§āļāļāļĩāđāļāļāļļāļĄāļēāļāđāļāđ
@Composable
fun PerformantAnimatedList(
items: List<Item>,
modifier: Modifier = Modifier
) {
// Calculate once whether the list is empty
val isEmpty by remember {
derivedStateOf { items.isEmpty() }
}
LazyColumn(modifier = modifier) {
items(
items = items,
key = { it.id }
) { item ->
// Lightweight animation only on initial appearance
var hasAppeared by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
hasAppeared = true
}
val alpha by animateFloatAsState(
targetValue = if (hasAppeared) 1f else 0f,
animationSpec = tween(200),
label = "itemAlpha"
)
ItemCard(
item = item,
modifier = Modifier.graphicsLayer { this.alpha = alpha }
)
}
}
}āļāļāļŠāļĢāļļāļ
āđāļāļāļīāđāļĄāļāļąāļāđāļ Jetpack Compose āļĄāļāļāļāļ§āļēāļĄāļŠāļĄāļāļļāļĨāļĢāļ°āļŦāļ§āđāļēāļāļāļ§āļēāļĄāļāđāļēāļĒāđāļāļāļēāļĢāđāļāđāļāļēāļāđāļĨāļ°āļāļēāļĢāļāļ§āļāļāļļāļĄāļāļąāđāļāļŠāļđāļ āļāļĩāđāļāļ·āļāļāļĢāļ°āđāļāđāļāļŠāļģāļāļąāļāļāļĩāđāļāļ§āļĢāļāļāļāļģ:
- â āđāļĨāļ·āļāļāļĢāļ°āļāļąāļ API āļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļĄāļāļēāļĄāļāļ§āļēāļĄāļāļąāļāļāđāļāļ (AnimatedVisibility â animate*AsState â Animatable)
- â
āđāļāđ
updateTransitionāđāļāļ·āđāļāļāļĢāļ°āļŠāļēāļāļŦāļĨāļēāļĒāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāđāļāļĩāđāļĒāļ§āļāđāļāļāļāļąāļ - â
āđāļĨāļ·āļāļ
springāļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļāļāļĩāđāđāļāđāļāļāļĢāļĢāļĄāļāļēāļāļī āđāļĨāļ°tweenāļŠāļģāļŦāļĢāļąāļāļĢāļ°āļĒāļ°āđāļ§āļĨāļēāļāļĩāđāđāļĄāđāļāļĒāļģ - â
āļĢāļ°āļāļļāļāļēāļĢāļēāļĄāļīāđāļāļāļĢāđ
keyāļāļĩāđāđāļŠāļāļĩāļĒāļĢāđāļŠāļĄāļāļŠāļģāļŦāļĢāļąāļāđāļāļāļīāđāļĄāļāļąāļāļĢāļēāļĒāļāļēāļĢ - â
āļāļĢāļąāļāđāļāđāļāļāđāļ§āļĒ
graphicsLayerāđāļāļ·āđāļāļŦāļĨāļĩāļāđāļĨāļĩāđāļĒāļāļāļēāļĢ recompose āļāļĩāđāđāļĄāđāļāļģāđāļāđāļ - â āļāļāļŠāļāļāđāļāļāļīāđāļĄāļāļąāļāļāļāļāļļāļāļāļĢāļāđāļāļĢāļīāļāđāļāļ·āđāļāļĒāļ·āļāļĒāļąāļāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļ
āļāļēāļĢāđāļāļĩāđāļĒāļ§āļāļēāļāđāļāļāļīāđāļĄāļāļąāļ Compose āļāļģāđāļŦāđāđāļāļ Android āļĢāļ°āļāļąāļāļĄāļ·āļāļāļēāļāļĩāļāđāļāļāļāđāļēāļāļāļēāļāļāļĩāđāļāļ·āđāļ āđāļāļāļāļīāļāđāļŦāļĨāđāļēāļāļĩāđāļāļŠāļĄāļāļŠāļēāļāļāļąāļāļāļēāļĢāđāļŦāđāļāļ§āļēāļĄāļŠāļģāļāļąāļāļāļąāļāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļ āļāļģāđāļŦāđāļŠāļēāļĄāļēāļĢāļāļŠāļĢāđāļēāļāļāļĢāļ°āļŠāļāļāļēāļĢāļāđāļāļđāđāđāļāđāļāļĩāđāļĨāļ·āđāļāđāļŦāļĨāđāļĨāļ°āļāđāļēāļŠāļāđāļ
āđāļĢāļīāđāļĄāļāļķāļāļāđāļāļĄāđāļĨāļĒ!
āļāļāļŠāļāļāļāļ§āļēāļĄāļĢāļđāđāļāļāļāļāļļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāļŠāļąāļĄāļ āļēāļĐāļāđāđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āđāļāđāļ
āđāļāļĢāđ
āļāļāļāļ§āļēāļĄāļāļĩāđāđāļāļĩāđāļĒāļ§āļāđāļāļ

20 āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ Jetpack Compose āļĒāļāļāļāļīāļĒāļĄāļāļĢāļ°āļāļģāļāļĩ 2026
20 āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ Jetpack Compose āļāļĩāđāļāļāļāđāļāļĒāļāļĩāđāļŠāļļāļ: recomposition, āļāļēāļĢāļāļąāļāļāļēāļĢ state, navigation, āļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļ āđāļĨāļ° pattern āļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ āļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļāļĨāļ°āđāļāļĩāļĒāļ

Kotlin 2.3 āļŠāļģāļŦāļĢāļąāļ Android: Name-Based Destructuring, KMP āđāļĨāļ°āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ 2026
āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ Kotlin 2.3 āļŠāļģāļŦāļĢāļąāļāļāļąāļāļāļąāļāļāļē Android āđāļāļāļĩ 2026 āļāļĢāļāļāļāļĨāļļāļĄ name-based destructuring, KMP, context parameters, Flow āđāļĨāļ° coroutines āļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļ

MVVM vs MVI āļāļ Android: āđāļĨāļ·āļāļ Architecture āđāļŦāļāļāļĩāđāļāļāļĩ 2026?
āđāļāļĢāļĩāļĒāļāđāļāļĩāļĒāļ MVVM āđāļĨāļ° MVI āļāļ Android āļāļĒāđāļēāļāļĨāļ°āđāļāļĩāļĒāļ: āļāđāļāļāļĩāļāđāļāđāļŠāļĩāļĒ āļāļĢāļāļĩāļāļēāļĢāđāļāđāļāļēāļ āđāļĨāļ°āļāļđāđāļĄāļ·āļāļāļāļīāļāļąāļāļīāđāļāļāļēāļĢāđāļĨāļ·āļāļ architecture āļāļĩāđāđāļŦāļĄāļēāļ°āļŠāļĄāđāļāļāļĩ 2026