Oluşturma'daki Animasyonlar için hızlı rehber

Compose'da birçok yerleşik animasyon mekanizması vardır ve hangisini seçeceğinizi bilmek zor olabilir. Aşağıda, animasyonun yaygın kullanım alanlarının bir listesi verilmiştir. Kullanabileceğiniz farklı API seçeneklerinin tamamı hakkında daha ayrıntılı bilgi için Compose Animation belgelerinin tamamını okuyun.

Yaygın composable özelliklere animasyon ekleme

Compose, birçok yaygın animasyon kullanım alanını çözmenize olanak tanıyan kullanışlı API'ler sunar. Bu bölümde, bir composable'ın yaygın özelliklerini nasıl animasyonlu hale getirebileceğiniz gösterilmektedir.

Görünme / kaybolma animasyonu

Yeşil renkli bir composable gösterilip gizleniyor.
Şekil 1. Bir sütundaki öğenin görünme ve kaybolma animasyonu

Bir Composable'ı gizlemek veya göstermek için AnimatedVisibility kullanın. AnimatedVisibility içindeki alt öğeler, kendi giriş veya çıkış geçişleri için Modifier.animateEnterExit() kullanabilir.

var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
    // your composable here
    // ...
}

AnimatedVisibility öğesinin enter ve exit parametreleri, bir composable'ın göründüğünde ve kaybolduğunda nasıl davranacağını yapılandırmanıza olanak tanır. Daha fazla bilgi için belgelerin tamamını okuyun.

Bir composable'ın görünürlüğünü canlandırmanın başka bir yolu da animateFloatAsState kullanarak zaman içinde alfayı canlandırmaktır:

var visible by remember {
    mutableStateOf(true)
}
val animatedAlpha by animateFloatAsState(
    targetValue = if (visible) 1.0f else 0f,
    label = "alpha"
)
Box(
    modifier = Modifier
        .size(200.dp)
        .graphicsLayer {
            alpha = animatedAlpha
        }
        .clip(RoundedCornerShape(8.dp))
        .background(colorGreen)
        .align(Alignment.TopCenter)
) {
}

Ancak alfa değerini değiştirirken, composable'ın oluşturmada kaldığını ve yerleştirildiği alanı kaplamaya devam ettiğini unutmayın. Bu durum, ekran okuyucuların ve diğer erişilebilirlik mekanizmalarının öğeyi hâlâ ekranda olarak değerlendirmesine neden olabilir. Öte yandan AnimatedVisibility, öğeyi sonunda kompozisyondan kaldırır.

Bir composable'ın alfa değerini canlandırma
Şekil 2. Bir composable'ın alfa değerini canlandırma

Arka plan rengine animasyon ekle

Arka plan rengi zaman içinde animasyon olarak değişen ve renklerin birbirine karıştığı bir composable.
Şekil 3. Birleştirilebilir öğenin arka plan rengini canlandırma

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

Bu seçenek, Modifier.background() kullanmaktan daha yüksek performans sağlar. Modifier.background(), tek seferlik renk ayarı için kabul edilebilir ancak bir rengi zaman içinde canlandırırken gereğinden fazla yeniden oluşturmaya neden olabilir.

Arka plan rengini sonsuz animasyonlu hale getirmek için animasyon tekrarlama bölümüne bakın.

Bir composable'ın boyutunu animasyonlu hale getirme

Boyut değişikliğini sorunsuz bir şekilde canlandıran yeşil composable.
Şekil 4. Küçük ve büyük boyut arasında sorunsuz bir şekilde animasyon oluşturan Composable

Compose, composable'ların boyutunu birkaç farklı şekilde canlandırmanıza olanak tanır. Oluşturulabilir boyut değişiklikleri arasındaki animasyonlar için animateContentSize() kodunu kullanın.

Örneğin, bir satırdan birden fazla satıra genişleyebilen metin içeren bir kutunuz varsa daha yumuşak bir geçiş elde etmek için Modifier.animateContentSize() kullanabilirsiniz:

var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

Boyut değişikliklerinin nasıl gerçekleşmesi gerektiğini açıklamak için SizeTransform ile AnimatedContent özelliğini de kullanabilirsiniz.

Birleştirilebilir öğenin konumuna animasyon ekleme

Yeşil composable, sorunsuz bir şekilde aşağı ve sağa doğru animasyonlu olarak hareket ediyor.
Şekil 5. Ofset kadar hareket eden bileşen

Bir composable'ın konumuna animasyon eklemek için Modifier.offset{ } ile birlikte animateIntOffsetAsState() kullanın.

var moved by remember { mutableStateOf(false) }
val pxToMove = with(LocalDensity.current) {
    100.dp.toPx().roundToInt()
}
val offset by animateIntOffsetAsState(
    targetValue = if (moved) {
        IntOffset(pxToMove, pxToMove)
    } else {
        IntOffset.Zero
    },
    label = "offset"
)

Box(
    modifier = Modifier
        .offset {
            offset
        }
        .background(colorBlue)
        .size(100.dp)
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            moved = !moved
        }
)

Konum veya boyut animasyonu sırasında composable'ların diğer composable'ların üzerine veya altına çizilmediğinden emin olmak istiyorsanız Modifier.layout{ } kullanın. Bu değiştirici, boyut ve konum değişikliklerini üst öğeye yayar. Bu değişiklikler diğer alt öğeleri etkiler.

Örneğin, bir Box öğesini Column içinde taşıyorsanız ve Box taşındığında diğer alt öğelerin de taşınması gerekiyorsa Modifier.layout{ } ile birlikte ofset bilgilerini aşağıdaki gibi ekleyin:

var toggled by remember {
    mutableStateOf(false)
}
val interactionSource = remember {
    MutableInteractionSource()
}
Column(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxSize()
        .clickable(indication = null, interactionSource = interactionSource) {
            toggled = !toggled
        }
) {
    val offsetTarget = if (toggled) {
        IntOffset(150, 150)
    } else {
        IntOffset.Zero
    }
    val offset = animateIntOffsetAsState(
        targetValue = offsetTarget, label = "offset"
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
    Box(
        modifier = Modifier
            .layout { measurable, constraints ->
                val offsetValue = if (isLookingAhead) offsetTarget else offset.value
                val placeable = measurable.measure(constraints)
                layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) {
                    placeable.placeRelative(offsetValue)
                }
            }
            .size(100.dp)
            .background(colorGreen)
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
}

2. kutunun X,Y konumunu canlandırdığı 2 kutu, üçüncü kutu da kendisini Y miktarı kadar hareket ettirerek yanıt verir.
Şekil 6. Modifier.layout{ }
ile animasyon oluşturma

Bir composable'ın dolgusuna animasyon ekleme

Yeşil composable, tıklandığında küçülüp büyüyor ve dolgu animasyonlu olarak gösteriliyor.
Şekil 7. Dolgu animasyonu içeren Composable

Bir composable'ın dolgusunu canlandırmak için animateDpAsState ile birlikte Modifier.padding() kullanın:

var toggled by remember {
    mutableStateOf(false)
}
val animatedPadding by animateDpAsState(
    if (toggled) {
        0.dp
    } else {
        20.dp
    },
    label = "padding"
)
Box(
    modifier = Modifier
        .aspectRatio(1f)
        .fillMaxSize()
        .padding(animatedPadding)
        .background(Color(0xff53D9A1))
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            toggled = !toggled
        }
)

Birleştirilebilir öğenin yüksekliğini canlandırma

Şekil 8. Tıklandığında animasyonlu olarak yükselen bileşen

Bir composable'ın yüksekliğine animasyon eklemek için animateDpAsState ile birlikte Modifier.graphicsLayer{ } kullanın. Tek seferlik yükseklik değişiklikleri için Modifier.shadow() kullanın. Gölgeye animasyon uyguluyorsanız Modifier.graphicsLayer{ } değiştiricisini kullanmak daha performanslı bir seçenektir.

val mutableInteractionSource = remember {
    MutableInteractionSource()
}
val pressed = mutableInteractionSource.collectIsPressedAsState()
val elevation = animateDpAsState(
    targetValue = if (pressed.value) {
        32.dp
    } else {
        8.dp
    },
    label = "elevation"
)
Box(
    modifier = Modifier
        .size(100.dp)
        .align(Alignment.Center)
        .graphicsLayer {
            this.shadowElevation = elevation.value.toPx()
        }
        .clickable(interactionSource = mutableInteractionSource, indication = null) {
        }
        .background(colorGreen)
) {
}

Alternatif olarak, Card composable'ını kullanın ve yükseklik özelliğini her durum için farklı değerlere ayarlayın.

Metin ölçeği, çeviri veya döndürme animasyonu ekleme

Küçük ve büyük boyut arasında animasyonlu olarak geçiş yapan, "Hello" yazılı metin composable'ı.
Şekil 9. İki boyut arasında sorunsuz bir şekilde animasyon uygulanan metin

Metnin ölçeğini, çevirisini veya dönüşünü canlandırırken TextStyle üzerindeki textMotion parametresini TextMotion.Animated olarak ayarlayın. Bu sayede metin animasyonları arasında daha yumuşak geçişler olur. Metni çevirmek, döndürmek veya ölçeklendirmek için Modifier.graphicsLayer{ } simgesini kullanın.

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val scale by infiniteTransition.animateFloat(
    initialValue = 1f,
    targetValue = 8f,
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "scale"
)
Box(modifier = Modifier.fillMaxSize()) {
    Text(
        text = "Hello",
        modifier = Modifier
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
                transformOrigin = TransformOrigin.Center
            }
            .align(Alignment.Center),
        // Text composable does not take TextMotion as a parameter.
        // Provide it via style argument but make sure that we are copying from current theme
        style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated)
    )
}

Metin rengini canlandırma

"Hello Compose" kelimelerinin rengi yeşil ve mavi arasında değişiyor.
Şekil 10. Metin rengini animasyonlu hale getirme örneği

Metin rengine animasyon eklemek için BasicText composable'ında color lambda'sını kullanın:

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val animatedColor by infiniteTransition.animateColor(
    initialValue = Color(0xFF60DDAD),
    targetValue = Color(0xFF4285F4),
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "color"
)

BasicText(
    text = "Hello Compose",
    color = {
        animatedColor
    },
    // ...
)

Farklı içerik türleri arasında geçiş yapma

"Yükleniyor" yazan yeşil ekran, "Yüklendi" yazan mavi ekran ve "Hata" yazan beyaz ekran. Farklı composable'lar arasında basit bir animasyonla geçiş yapılıyor.
Şekil 11. Farklı composable'lar arasındaki değişikliklere animasyon eklemek için AnimatedContent kullanma (yavaşlatılmış)

Farklı composable'lar arasında animasyon oluşturmak için AnimatedContent kullanın. Composable'lar arasında standart bir geçiş istiyorsanız Crossfade kullanın.

var state by remember {
    mutableStateOf(UiState.Loading)
}
AnimatedContent(
    state,
    transitionSpec = {
        fadeIn(
            animationSpec = tween(3000)
        ) togetherWith fadeOut(animationSpec = tween(3000))
    },
    modifier = Modifier.clickable(
        interactionSource = remember { MutableInteractionSource() },
        indication = null
    ) {
        state = when (state) {
            UiState.Loading -> UiState.Loaded
            UiState.Loaded -> UiState.Error
            UiState.Error -> UiState.Loading
        }
    },
    label = "Animated Content"
) { targetState ->
    when (targetState) {
        UiState.Loading -> {
            LoadingScreen()
        }
        UiState.Loaded -> {
            LoadedScreen()
        }
        UiState.Error -> {
            ErrorScreen()
        }
    }
}

AnimatedContent, birçok farklı giriş ve çıkış geçişi gösterecek şekilde özelleştirilebilir. Daha fazla bilgi için AnimatedContent ile ilgili dokümanları veya AnimatedContent ile ilgili bu blog yayınını okuyun.

Farklı varış noktalarına giderken animasyonlu gösterim

Biri yeşil renkli "Landing" (Açılış), diğeri mavi renkli "Detail" (Ayrıntı) yazılı iki composable. Ayrıntı composable'ı, açılış composable'ının üzerine kayarak animasyonlu bir şekilde gösteriliyor.
Şekil 12. navigation-compose kullanarak composable'lar arasında animasyon oluşturma

navigation-compose yapısını kullanırken composable'lar arasındaki geçişleri canlandırmak için composable'da enterTransition ve exitTransition değerlerini belirtin. Ayrıca, tüm hedefler için kullanılacak varsayılan animasyonu en üst düzeyde NavHost ayarlayabilirsiniz:

val navController = rememberNavController()
NavHost(
    navController = navController, startDestination = "landing",
    enterTransition = { EnterTransition.None },
    exitTransition = { ExitTransition.None }
) {
    composable("landing") {
        ScreenLanding(
            // ...
        )
    }
    composable(
        "detail/{photoUrl}",
        arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }),
        enterTransition = {
            fadeIn(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideIntoContainer(
                animationSpec = tween(300, easing = EaseIn),
                towards = AnimatedContentTransitionScope.SlideDirection.Start
            )
        },
        exitTransition = {
            fadeOut(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideOutOfContainer(
                animationSpec = tween(300, easing = EaseOut),
                towards = AnimatedContentTransitionScope.SlideDirection.End
            )
        }
    ) { backStackEntry ->
        ScreenDetails(
            // ...
        )
    }
}

Gelen ve giden içeriğe farklı efektler uygulayan birçok farklı giriş ve çıkış geçişi türü vardır. Daha fazla bilgi için belgelere bakın.

Animasyonu tekrarlama

İki renk arasında animasyonla sonsuz bir şekilde mavi arka plana dönüşen yeşil arka plan.
Şekil 13. Arka plan rengi iki değer arasında sonsuz şekilde değişiyor

Animasyonunuzu sürekli tekrarlamak için rememberInfiniteTransition öğesini infiniteRepeatable animationSpec ile kullanın. İleri geri hareket etme şeklini belirtmek için RepeatModes değerini değiştirin.

Belirli sayıda tekrarlamak için repeatable kullanın.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Green,
    targetValue = Color.Blue,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(color)
    }
) {
    // your composable here
}

Bir composable başlatıldığında animasyon başlatma

LaunchedEffect, bir composable oluşturmaya girdiğinde çalışır. Bir composable başlatıldığında animasyon başlatır. Animasyon durum değişikliğini tetiklemek için bunu kullanabilirsiniz. Animasyonu başlatma sırasında başlatmak için animateTo yöntemiyle Animatable kullanma:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Sıralı animasyonlar oluşturma

Aralarında yeşil oklar bulunan dört daire, sırayla animasyonlu olarak gösteriliyor.
Şekil 14. Sıralı animasyonun nasıl ilerlediğini gösteren diyagram.

Sıralı veya eşzamanlı animasyonlar gerçekleştirmek için Animatable eş yordam API'lerini kullanın. Animatable üzerinde animateTo yöntemini art arda çağırmak, her animasyonun devam etmeden önce önceki animasyonların bitmesini beklemesine neden olur. Bunun nedeni, askıya alma işlevi olmasıdır.

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    alphaAnimation.animateTo(1f)
    yAnimation.animateTo(100f)
    yAnimation.animateTo(500f, animationSpec = tween(100))
}

Eşzamanlı animasyonlar oluşturma

Aynı anda birlikte hareket eden yeşil okların her birine doğru hareket ettiği üç daire.
Şekil 15. Eşzamanlı animasyonların aynı anda nasıl ilerlediğini gösteren diyagram.

Eşzamanlı animasyonlar elde etmek için eş yordam API'lerini (Animatable#animateTo() veya animate) ya da Transition API'sini kullanın. Bir eş yordam bağlamında birden fazla başlatma işlevi kullanırsanız animasyonlar aynı anda başlatılır:

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    launch {
        alphaAnimation.animateTo(1f)
    }
    launch {
        yAnimation.animateTo(100f)
    }
}

Aynı durumu kullanarak aynı anda birçok farklı özellik animasyonu oluşturmak için updateTransition API'yi kullanabilirsiniz. Aşağıdaki örnekte, durum değişikliğiyle kontrol edilen iki özellik (rect ve borderWidth) animasyonlu olarak gösterilmektedir:

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "transition")

val rect by transition.animateRect(label = "rect") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "borderWidth") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

Animasyon performansını optimize etme

Compose'daki animasyonlar performans sorunlarına neden olabilir. Bunun nedeni, animasyonun doğasıdır: Ekranda pikselleri hızlıca hareket ettirerek veya değiştirerek kare kare hareket illüzyonu oluşturmak.

Compose'un farklı aşamalarını (oluşturma, düzen ve çizim) göz önünde bulundurun. Animasyonunuz düzen aşamasını değiştiriyorsa etkilenen tüm composable'ların yeniden düzenlenmesi ve yeniden çizilmesi gerekir. Animasyonunuz çizim aşamasında gerçekleşiyorsa varsayılan olarak, animasyonu düzen aşamasında çalıştırmanıza kıyasla daha yüksek performans gösterir. Bunun nedeni, genel olarak daha az iş yapmasıdır.

Uygulamanızın animasyon sırasında mümkün olduğunca az işlem yapmasını sağlamak için mümkün olduğunda Modifier lambda sürümünü seçin. Bu, yeniden oluşturma işlemini atlar ve animasyonu oluşturma aşamasının dışında gerçekleştirir. Aksi takdirde, bu değiştirici her zaman çizim aşamasında çalıştığı için Modifier.graphicsLayer{ } kullanın. Bu konu hakkında daha fazla bilgi için performans belgelerindeki okuma işlemlerini erteleme bölümüne bakın.

Animasyon zamanlamasını değiştirme

Compose, çoğu animasyon için varsayılan olarak yay animasyonlarını kullanır. Yaylar veya fizik tabanlı animasyonlar daha doğal hissettirir. Ayrıca, sabit bir süre yerine nesnenin mevcut hızını hesaba kattıkları için kesintiye uğratılabilirler. Varsayılanı geçersiz kılmak isterseniz daha önce gösterilen tüm animasyon API'leri, animasyonun nasıl çalışacağını özelleştirmek için animationSpec ayarlama özelliğine sahiptir. Bu sayede animasyonun belirli bir süre boyunca çalışmasını veya daha esnek olmasını sağlayabilirsiniz.

Farklı animationSpec seçeneklerinin özeti aşağıda verilmiştir:

  • spring: Fizik kurallarına dayalı animasyon, tüm animasyonlar için varsayılan seçenektir. Farklı bir animasyon görünümü ve hissi elde etmek için sertliği veya sönümleme oranını değiştirebilirsiniz.
  • tween (between'in kısaltması): Süreye dayalı animasyon, Easing işleviyle iki değer arasında animasyon oluşturur.
  • keyframes: Animasyondaki belirli önemli noktalarda değerleri belirtmek için kullanılan spesifikasyon.
  • repeatable: RepeatMode tarafından belirtilen, belirli sayıda çalışan süreye dayalı spesifikasyon.
  • infiniteRepeatable: Süresiz olarak çalışan, süreye dayalı spesifikasyon.
  • snap: Animasyon olmadan anında bitiş değerine geçer.
Özellik ayarlanmamış bir animasyon ile özel yay özelliği ayarlanmış bir animasyon.
Şekil 16. Özellik grubu yok ve Özel Spring özellik grubu karşılaştırması

animationSpecs hakkında daha fazla bilgi için belgelerin tamamını okuyun.

Ek kaynaklar

Compose'daki eğlenceli animasyonlarla ilgili daha fazla örnek için aşağıdakilere göz atın: