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.

Android geliştiricileri için ileri düzey Jetpack Compose animasyonları

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.

Ön koşullar

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).

AnimationLevels.ktkotlin
// 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.

AnimatedVisibilityExample.ktkotlin
@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.

Geçişleri birleştirme

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.

AnimateAsStateExample.ktkotlin
@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.

TransitionExample.ktkotlin
// 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.

AnimatableExample.ktkotlin
@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.

AnimatedContentExample.ktkotlin
@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 ile performans

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.

InfiniteTransitionExample.ktkotlin
@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.

ListAnimationExample.ktkotlin
@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)
                }
            }
        }
    }
}
Liste animasyonları için kararlı anahtarlar

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.

CanvasAnimationExample.ktkotlin
@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ı.

PerformanceOptimization.ktkotlin
@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ı.

ListPerformance.ktkotlin
@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 updateTransition kullanmak
  • ✅ Doğal animasyonlar için spring, kesin süreler için tween tercih etmek
  • ✅ Liste animasyonları için her zaman kararlı bir key parametresi sağlamak
  • ✅ Gereksiz yeniden bestelemeleri önlemek için graphicsLayer ile 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

#jetpack compose
#android
#animations
#kotlin
#ui

Paylaş

İlgili makaleler