Jetpack Compose: Adım Adım İleri Düzey Animasyonlar
Compose'da ileri düzey animasyonlar için kapsamlı rehber: geçişler, AnimatedVisibility, Animatable, hareketler ve akıcı Android arayüzleri için performans.

Animasyonlar, işlevsel bir uygulamayı akılda kalıcı bir kullanıcı deneyimine dönüştürür. Jetpack Compose, akıcı arayüz oluşturmayı önemli ölçüde basitleştiren güçlü bir bildirimsel animasyon API'si sunar. Bu rehber, performanslı ve sürdürülebilir animasyonlar oluşturmak için ileri düzey teknikleri ele alır.
Bu eğitim, Compose temellerine (recomposition, state, modifier) aşinalık varsayar. Temeller için önce Jetpack Compose mülakat soruları rehberine göz atmak yararlı olur.
Compose'da Animasyon API Temelleri
Compose, animasyonlar için birkaç API seviyesi sunar. Seçim, istenen kontrol seviyesine ve animasyonun karmaşıklığına bağlıdır.
API üç ana kategoriye ayrılır: yüksek seviyeli animasyonlar (AnimatedVisibility, AnimatedContent), durum tabanlı animasyonlar (animate*AsState) ve düşük seviyeli animasyonlar (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)
}
}Doğru API seviyesini seçmek, gerekli esnekliği korurken okunabilir kod tutmak için kritiktir.
AnimatedVisibility: Zarif Giriş ve Çıkış Animasyonları
AnimatedVisibility, öğelerin görünme ve kaybolmalarını canlandırmak için ideal başlangıç noktasıdır. Bu API, içeriğin oluşturulmasını ve ayrıştırılmasını otomatik olarak yönetir.
enter ve exit parametreleri, animasyon davranışını tanımlayan geçiş kombinasyonlarını kabul eder. Bu geçişler + operatörü ile birleştirilebilir.
@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 içindeki içerik yalnızca visible = true olduğunda oluşturulur ve bu durum, çok sayıda genişletilebilir öğe içeren listelerde performansı optimize eder.
Kullanılabilir geçişler arasında fadeIn/fadeOut, slideIn/slideOut, expandIn/shrinkOut, scaleIn/scaleOut bulunur. Özel efektler oluşturmak için serbestçe birleştirilebilirler.
animate*AsState: Durum Odaklı Animasyonlar
animate*AsState fonksiyon ailesi, ilkel değerlerdeki değişiklikleri otomatik olarak canlandırır. Compose'daki basit animasyonlar için en deyimsel yaklaşımdır.
Her veri türünün kendine özel bir fonksiyonu vardır: animateColorAsState, animateFloatAsState, animateDpAsState, animateIntAsState vb.
@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 parametresi, animasyonun zamanlama davranışını kontrol eder. En çok kullanılan iki spec, tween (easing ile sabit süre) ve spring (sıçramalı gerçekçi fizik) şeklindedir.
Transition: Birden Çok Animasyonu Düzenlemek
Birden fazla özellik koordineli bir şekilde canlandırılması gerektiğinde, updateTransition merkezi kontrol sağlar. Bu API, tüm animasyonların senkronize kalmasını garanti eder.
Desen, bir enum durumu tanımlamayı ve ardından bu duruma bağlı her özellik için animasyon oluşturmayı içerir.
// 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 mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Animatable: Animasyon Üzerinde Tam Kontrol
Animatable, tam programatik kontrol sağlayan düşük seviyeli API'dir. Bu yaklaşım, kesintiye uğratılabilir animasyonlar, hareketler veya karmaşık senaryolar için gereklidir.
animate*AsState'in aksine, Animatable devam eden bir animasyonu bitmesini beklemeden durdurmayı, tersine çevirmeyi veya değiştirmeyi mümkün kılar.
@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'ın temel yöntemleri animateTo() (bir hedefe doğru animasyon), snapTo() (anlık değişim) ve stop() (kesinti) şeklindedir.
AnimatedContent: İçerik Geçişleri
AnimatedContent, farklı içerikler arasındaki geçişleri canlandırır. Bu API, görüntülenen arayüzü tamamen değiştiren durum değişiklikleri için mükemmeldir.
targetState anahtarı, bir geçişin ne zaman gerçekleşeceğini belirler. transitionSpec, çıkan ve giren içeriğin nasıl etkileşime gireceğini tanımlar.
@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 içindeki içerik, her targetState değişikliğinde yeniden oluşturulur. Karmaşık içerik için maliyetli öğeleri belleğe almak veya uygun bir anahtar stratejisi kullanmak yararlıdır.
rememberInfiniteTransition ile Sonsuz Animasyonlar
Döngü halindeki animasyonlar (yükleme göstergeleri, nabız efektleri) için rememberInfiniteTransition, manuel döngü yönetimi gerektirmeyen özel bir API sunar.
@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 ile Liste Animasyonları
Liste öğeleri için animasyonlar özel ilgi gerektirir. animateItem() modifier'ı (eskiden animateItemPlacement), yeniden sıralamaları otomatik olarak canlandırır.
@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)
}
}
}
}
}Kararlı bir key parametresi olmadan animateItem, öğeleri yeniden bestelemeler arasında izleyemez. Liste indeksi yerine benzersiz bir kimlik kullanılmalı.
Canvas Animasyonları
Özel görsel efektler için Canvas'ı animasyonlu değerlerle birleştirmek tam esneklik sunar.
@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 mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Animasyon Performans Optimizasyonu
Kötü optimize edilmiş animasyonlar takılmalara yol açabilir ve pili tüketebilir. 60 FPS'i korumak için en iyi uygulamalar şöyle.
İlk kural, animasyon sırasında tahsislerden kaçınmaktır. Yeniden bestelemeleri tetikleyen modifier'lar yerine graphicsLayer kullanılmalı.
@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()
}
}İkinci kritik nokta listelerdeki animasyonlardır. Eş zamanlı animasyon sayısı sınırlandırılmalı ve türetilmiş hesaplamalar için derivedStateOf kullanılmalı.
@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 }
)
}
}
}Sonuç
Jetpack Compose'daki animasyonlar, kullanım kolaylığı ile ileri düzey kontrol arasında bir denge sunar. Akılda tutulması gereken anahtar noktalar:
- ✅ Karmaşıklığa göre uygun API seviyesini seçmek (AnimatedVisibility → animate*AsState → Animatable)
- ✅ İlişkili birden çok animasyonu koordine etmek için
updateTransitionkullanmak - ✅ Doğal animasyonlar için
spring, kesin süreler içintweentercih etmek - ✅ Liste animasyonları için her zaman kararlı bir
keyparametresi sağlamak - ✅ Gereksiz yeniden bestelemeleri önlemek için
graphicsLayerile optimize etmek - ✅ Performansı doğrulamak için animasyonları gerçek cihazlarda test etmek
Compose animasyonlarına hakim olmak, profesyonel Android uygulamalarını ayırt eder. Performansa dikkat ile birleştirilen bu teknikler, akıcı ve etkileyici kullanıcı deneyimleri oluşturmayı mümkün kılar.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

2026 Yilinda En Cok Sorulan 20 Jetpack Compose Mulakat Sorusu
En sik sorulan 20 Jetpack Compose mulakat sorusu: recomposition, state yonetimi, navigasyon, performans ve mimari desenler.

Android'de MVVM vs MVI: 2026'da Hangi Mimariyi Seçmeli?
Android'de MVVM ve MVI karşılaştırması: avantajlar, kısıtlamalar, kullanım senaryoları ve 2026'da doğru mimariyi seçmek için pratik rehber.

Kotlin Coroutines Rehberi: Android için 2026 Kılavuzu
Android geliştirmede Kotlin coroutines konusunda uzmanlaşın: suspend fonksiyonlar, scope yapıları, dispatcher'lar ve ileri düzey kalıplar.