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

Compose'da birçok yerleşik animasyon mekanizması vardır ve hangisinin seçileceğini belirlemek zor olabilir. Aşağıda, animasyonların yaygın kullanım alanlarının bir listesi yer almaktadır. Kullanabileceğiniz farklı API seçeneklerinin tümü hakkında daha ayrıntılı bilgi için Animasyon Oluşturma dokümanlarının tamamını okuyun.

Ortak composable özelliklerini canlandırma

Compose, birçok yaygın animasyon kullanım alanında çözüm bulmanıza olanak tanıyan kullanışlı API'ler sunar. Bu bölümde, bir composable'ın ortak özelliklerini nasıl canlandırabileceğiniz gösterilmektedir.

Görünen / kaybolan animasyon

Kendisini gizleyen yeşil composable
Şekil 1. Sütundaki bir öğenin görünümünü ve kaybolmasını canlandırma

Bir Besteci'yi gizlemek veya göstermek için AnimatedVisibility simgesini kullanın. AnimatedVisibility içindeki çocuklar 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 giriş ve çıkış parametreleri, bir composable'ın görünüp kaybolduğunda nasıl davranacağını yapılandırmanıza olanak tanır. Daha fazla bilgi için tüm belgeleri okuyun.

Bir composable'ın görünürlüğünü canlandırmanın bir diğer yolu da animateFloatAsState kullanarak alfayı zaman içinde 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)
) {
}

Bununla birlikte, alfayı değiştirmek, composable'ın bestede kaldığı ve yerleştirildiği alanı kapmaya devam ettiği uyarısını da beraberinde getirir. Bu durum, ekran okuyucuların ve diğer erişilebilirlik mekanizmalarının ekrandaki öğeyi düşünmeye devam etmesine yol açabilir. Diğer yandan, AnimatedVisibility sonuçta bu öğeyi besteden kaldırır.

Bir composable'ın alfasına animasyon ekleme
Şekil 2. Bir composable'ın alfa sürümüne animasyon ekleme

Arka plan rengini canlandır

Renklerin birbirine dönüştüğü bir animasyon olarak zaman içinde değişen arka plan rengiyle oluşturulabilir.
Şekil 3. composable'ın arka plan rengine animasyon ekleme

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() kullanımına kıyasla daha etkilidir. Modifier.background(), tek seferlik renk ayarı için kabul edilebilir. Ancak bir renk zaman içinde canlandırılırken bu durum, gereğinden fazla yeniden besteye neden olabilir.

Arka plan rengini sonsuza kadar canlandırmak için animasyonları tekrarlama bölümünü inceleyin.

Bir composable'ın boyutunu canlandırma

Yeşil composable, boyut değişimine sorunsuz şekilde dokunuyor.
Şekil 4. Küçük ve büyük boyutlar arasında sorunsuz animasyonlar oluşturma

Oluşturma, composable'ların boyutunu birkaç farklı şekilde canlandırmanızı sağlar. Birleştirilebilir boyut değişiklikleri arasındaki animasyonlar için animateContentSize() değerini kullanın.

Örneğin, birden çok satıra genişleyebilen metin içeren bir kutunuz varsa daha yumuşak bir geçiş için Modifier.animateContentSize() öğesini 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 yapılması gerektiğini açıklamak için AnimatedContent ile birlikte SizeTransform kullanabilirsiniz.

Bir composable'ın konumunu canlandır

Aşağı ve sağa doğru düzgün şekilde animasyon yapan yeşil composable
Şekil 5. Ofsetle derlenebilir taşıma

Bir composable'ın konumuna animasyon eklemek için Modifier.offset{ } öğesini animateIntOffsetAsState() ile birlikte 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
        }
)

Konumu veya boyutu canlandırırken composable'ların diğer composable'ların üzerine veya altına çizilmediğinden emin olmak için Modifier.layout{ } kullanın. Bu değiştirici, boyut ve konum değişikliklerini üst öğeye yayar ve ardından diğer alt öğeleri etkiler.

Örneğin, bir Box öğesini Column içinde taşıyorsanız ve Box hareket ettiğinde diğer alt öğelerin de hareket etmesi gerekiyorsa ofset bilgilerini Modifier.layout{ } ile 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. kutu,X, Y konumunu canlandırıyor; üçüncü kutu da Y miktarında hareket ederek yanıt veriyor.
Şekil 6. Modifier.layout{ } ile animasyon oluşturma

Bir composable'ın dolgusunu canlandırma

Yeşil composable, tıklandığında küçülüyor ve dolgu canlandırılıyor
Şekil 7. Dolgusu canlandırmasıyla oluşturulabilir

Bir composable'ın dolgusunu canlandırmak için animateDpAsState öğesini Modifier.padding() ile birlikte 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
        }
)

Bir composable'ın yüksekliğini gösterme

Şekil 8. Composable'ın tıklamayla animasyon eklemesi

Bir composable'ın yüksekliğine animasyon eklemek için animateDpAsState öğesini Modifier.graphicsLayer{ } ile birlikte kullanın. Tek seferlik yükseklik değişiklikleri için Modifier.shadow() değerini kullanın. Gölgeyi canlandırıyorsanız Modifier.graphicsLayer{ } değiştiricisini kullanmak daha yüksek performanslı 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'ı kullanın ve yükseltme özelliğini durum başına farklı değerlere ayarlayın.

Metin ölçeğini, çevirisini veya döndürmeyi canlandırma

"Şöyle" anlamına gelen metin
Şekil 9. İki boyut arasında metin animasyonu sorunsuz çalışıyor

Metnin ölçeğini, çevrilmesini veya döndürülmesini canlandırırken TextStyle öğesindeki textMotion parametresini TextMotion.Animated olarak ayarlayın. Bu sayede metin animasyonları arasında daha yumuşak geçişler sağlanır. Metni çevirmek, döndürmek veya ölçeklendirmek için Modifier.graphicsLayer{ } aracını 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ır

Kelimeler
Şekil 10. Animasyonlu metin rengini gösteren örnek

Metin rengine animasyon eklemek için BasicText composable'da color lambda 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ş yapın

"Yeşil ekran"da şöyle diyor:
Şekil 11. Farklı composable'lar arasındaki değişiklikleri canlandırmak için Animasyonlu İçeriği kullanma (yavaşlama)

Farklı composable'lar arasında animasyon oluşturmak için AnimatedContent kullanın. composable'lar arasında yalnızca standart 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 türünü gösterecek şekilde özelleştirilebilir. Daha fazla bilgi için AnimatedContent ile ilgili dokümanları veya şuradaki blog yayınını okuyun: AnimatedContent.

Farklı hedeflere giderken animasyon kullan

İki composable, biri yeşil, diğeri "Açılış", diğeri "Detay" (Detay) yazısı ve composable'ı, composable'ın üzerine kaydırarak animasyon yapıyor.
Şekil 12. navigasyon-compose'u kullanarak composable'lar arasında animasyon oluşturma

Gezinme-compose yapısını kullanırken composable'lar arasındaki geçişleri canlandırmak için bir composable'da enterTransition ve exitTransition özelliklerini belirtin. Üst düzey NavHost, tüm hedefler için kullanılacak varsayılan animasyonu da 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 sürekli olarak mavi bir arka plana dönüşen yeşil bir arka plan.
Şekil 13. İki değer arasında sonsuza kadar animasyonlu arka plan rengi

Animasyonunuzu sürekli olarak tekrarlamak için rememberInfiniteTransition öğesini infiniteRepeatable animationSpec ile kullanın. RepeatModes öğesini değiştirerek nasıl ileri geri gitmesi gerektiğini belirtin.

Belirli sayıda tekrarlamak için finiteRepeatable öğesini 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şlat

LaunchedEffect, besteye bir composable girdiğinde çalışır. Bir composable'ın lansmanı yapıldığında animasyon başlatır. Animasyon durumu değişikliğini sağlamak için bunu kullanabilirsiniz. Başlatma sırasında animasyonu başlatmak için Animatable ile animateTo yöntemi kullanıldığında:

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

Sıralı animasyonlar oluşturma

Birbirinin arasında yeşil okların olduğu ve birbiri ardına hareket eden dört daire.
Şekil 14. Sıralı animasyonun tek tek nasıl ilerlediğini gösteren şema.

Sıralı veya eşzamanlı animasyonlar gerçekleştirmek için Animatable eş zamanlı API'lerini kullanın. Diğerinden sonra Animatable için animateTo çağrısının yapılması, her animasyonun devam etmeden önce önceki animasyonların tamamlanmasını beklemesine neden olur . Bunun nedeni, bunun bir 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

Yeşil okların olduğu ve hepsi aynı anda canlandıran üç daire.
Şekil 15. Eşzamanlı animasyonların aynı anda nasıl ilerlediğini gösteren şema.

Eşzamanlı animasyonlar elde etmek için eş anlamlı API'leri (Animatable#animateTo() veya animate) ya da Transition API'yi kullanın. Eş yordamlarda birden çok başlatma işlevi kullanırsanız animasyonları aynı anda başlatır:

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

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

Aynı anda birçok farklı özellik animasyonunu çalıştırmak amacıyla aynı durumu kullanmak için updateTransition API'yi kullanabilirsiniz. Aşağıdaki örnekte, durum değişikliği (rect ve borderWidth) tarafından kontrol edilen iki özellik 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 et

Compose'daki animasyonlar performans sorunlarına neden olabilir. Bunun nedeni, animasyonun doğası gereği hareket illüzyonu oluşturmak için ekrandaki pikselleri kare kare hızlı bir şekilde taşımak veya değiştirmektir.

İçerik oluşturmanın farklı aşamalarını göz önünde bulundurun: kompozisyon, düzen ve çizim. Animasyonunuz düzen aşamasını değiştirirse etkilenen tüm composable'ların yeniden düzenlenip yeniden çizilmesi gerekir. Animasyonunuz çizim aşamasında gerçekleşiyorsa, animasyonun düzen aşamasında çalıştırılmasına göre varsayılan olarak daha yüksek performanslı olacaktır. Çünkü, genel olarak yapılması gereken daha az iş olur.

Uygulamanızın animasyon yaparken mümkün olduğunca az performans göstermesini sağlamak için mümkün olduğunda bir Modifier'nin lambda sürümünü seçin. Bu işlem, yeniden bestelemeyi atlar ve animasyonu beste 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ılır. Bu konuda daha fazla bilgi için performans belgelerindeki okuma işlemlerini erteleme bölümüne bakın.

Animasyon zamanlamasını değiştirme

Oluşturma işleminde varsayılan olarak, çoğu animasyon için yay animasyonları kullanılır. Yaylar ya da fizik tabanlı animasyonlar daha doğal görünür. Ayrıca sabit bir süre yerine nesnenin o anki hızı da hesaba katıldığından kesintiye uğrayabilirler. Varsayılanı geçersiz kılmak isterseniz yukarıda gösterilen tüm animasyon API'leri, belirli bir süre boyunca yürütülmesini veya daha fazla atlama yapmasını istediğinizde animasyonun çalışma şeklini özelleştirmek için animationSpec ayarlayabilir.

Aşağıda, farklı animationSpec seçeneklerinin özeti verilmiştir:

  • spring: Tüm animasyonlar için varsayılan olan fizik tabanlı animasyon. Farklı bir animasyon görünümü ve hissi elde etmek için sertliği veya dampingRatio'yu değiştirebilirsiniz.
  • tween (arasında): Süreye dayalı animasyon, iki değer arasında bir Easing işleviyle animasyon.
  • keyframes: Bir animasyonda belirli temel noktalarda değerleri belirtmeyle ilgili spesifikasyon.
  • repeatable: RepeatMode tarafından belirtilen, belirli sayıda çalıştırılan süreye dayalı spesifikasyon.
  • infiniteRepeatable: Sonsuza kadar çalışan, süreye dayalı spesifikasyon.
  • snap: Animasyon olmadan bitiş değerine anında tutturur.
Alternatif metninizi buraya yazın
Şekil 16. Spesifikasyon ayarlanmamış ve Custom Spring spesifikasyon grubu karşılaştırması

animationSpecs hakkında daha fazla bilgi için dokümanların tamamını okuyun.

Ek kaynaklar

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