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, animasyonların 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 dokümanının tamamını okuyun.

Yaygın composable özelliklerini canlandırma

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 ortak özelliklerini nasıl canlandırabileceğiniz gösterilmektedir.

Görünme / kaybolma animasyonu

Yeşil composable'ın gösterilmesi ve gizlenmesi
Şekil 1. Bir öğenin sütunda görünme ve kaybolma animasyonu

Bir Composable'ı gizlemek veya göstermek için AnimatedVisibility öğesini kullanın. AnimatedVisibility içindeki çocuklar kendi giriş veya çıkış geçişleri için Modifier.animateEnterExit() öğesini 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, 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 bir diğer yolu da animateFloatAsState kullanarak alfa değerini 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)
) {
}

Ancak alfa değerini değiştirirken, composable'ın kompozisyonda kalmaya devam edeceği ve yerleştirildiği alanı kaplamaya devam edeceği unutulmamalıdır. Bu durum, ekran okuyucuların ve diğer erişilebilirlik mekanizmalarının ekrandaki öğeyi dikkate almaya devam etmesine neden olabilir. Diğer yandan, AnimatedVisibility öğeyi kompozisyondan kaldırır.

Bir composable'ın alfa değerine animasyon uygulama
Şekil 2. Bir composable'ın alfa değerini animasyona dönüştürme

Arka plan rengini animasyonla değiştirme

Renklerin birbirine karıştığı, zaman içinde değişen arka plan rengiyle animasyon olarak oluşturulabilir.
Şekil 3. Composable'ın arka plan rengine animasyon uygulama

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 iyi performans gösterir. Modifier.background(), tek seferlik renk ayarı için kabul edilebilir ancak zaman içinde bir renge animasyon uygularken gerekenden daha fazla yeniden oluşturmaya neden olabilir.

Arka plan rengini sonsuza kadar canlandırmak için animasyonu tekrarlama bölümüne bakın.

Bir composable'ın boyutunu canlandırma

Boyut değişikliğini sorunsuz bir şekilde animasyonla gösteren yeşil renkli composable.
Şekil 4. Küçük ve büyük boyut arasında sorunsuz bir şekilde animasyon oluşturan Composable'lar

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

Örneğin, bir satırdan birden fazla satıra genişleyebilen metin içeren bir kutunuz varsa daha sorunsuz bir geçiş 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 yapılması gerektiğini açıklamak için AnimatedContent ile birlikte SizeTransform de kullanabilirsiniz.

Composable'ın konumunu animasyonla değiştirme

Aşağı ve sağa doğru sorunsuz bir şekilde animasyon oluşturan yeşil birleştirilebilir
Şekil 5. Bileştirilebilir hareket ettirme (kaydırma)

Bir composable'ın konumunu canlandırmak için Modifier.offset{ } ile animateIntOffsetAsState()'ı 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
        }
)

Konum veya boyut animasyonu yaparken composable'ların diğer composable'ların üzerinde ya da altında çizilmemesini sağlamak istiyorsanız Modifier.layout{ } kullanın. Bu değiştirici, boyut ve konum değişikliklerini üst öğeye yayar. Bu da diğer alt öğeleri etkiler.

Örneğin, bir Box öğesini Column içinde taşıyorsanız ve diğer alt öğelerin Box taşındığında taşınması gerekiyorsa Modifier.layout{ } ile birlikte şu şekilde dengeleme bilgilerini 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 var. İkinci kutunun X,Y konumu animasyonlu. Üçüncü kutu da Y miktarı kadar hareket ederek yanıt veriyor.
Şekil 6. Modifier.layout{ } ile animasyon oluşturma

Bir composable'ın dolgusunu canlandırma

Tıklama üzerine küçülen ve büyüyen, dolgunluğu animasyonlu yeşil birleştirilebilir öğe
Şekil 7. Dolgu animasyonuyla birlikte kullanılabilir

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

Şekil 8. Tıklama sırasında Composable'ın yüksekliğinin animasyonla değiştirilmesi

Bir composable'ın yüksekliğini canlandırmak için animateDpAsState ile Modifier.graphicsLayer{ }'ı birlikte kullanın. Tek seferlik yükseklik değişiklikleri için Modifier.shadow() simgesini kullanın. Gölgeyi animasyonlandırıyorsanız Modifier.graphicsLayer{ } değiştiricisini kullanmak daha iyi performans sağlar.

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 duruma göre farklı değerlere ayarlayın.

Metin ölçeğine, çeviriye veya döndürmeye animasyon ekleme

Küçük ve büyük boyut arasında animasyonlu olarak değişen "Merhaba" metnini içeren composable.
Şekil 9. İki boyut arasında sorunsuz şekilde animasyonlu metin

Metnin ölçeğini, çevirisini veya dönüşünü animasyonla gösterirken textMotion parametresini TextStyle üzerinde TextMotion.Animated olarak ayarlayın. Bu sayede metin animasyonları arasında daha sorunsuz geçişler sağlanır. 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 canlandırma örneği

Metin rengini canlandırmak için BasicText composable'da 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 basit bir animasyonla tekrarlanıyor.
Şekil 11. Farklı composable'lar arasındaki değişikliklere animasyon eklemek için AnimatedContent'i kullanma (yavaşlatılmış)

Farklı composable'lar arasında animasyon oluşturmak için AnimatedContent kullanın. Composables arasında yalnızca 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şini 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ı hedeflere giderken animasyonları kullanma

Biri "Landing" (Açılış) yazan yeşil, diğeri "Detail" (Ayrıntı) yazan mavi olmak üzere iki composable. Ayrıntı composable'ı, açılış composable'ının üzerine kaydırılarak animasyon oluşturuluyor.
Şekil 12. navigation-compose kullanarak composable'lar arasında animasyon oluşturma

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

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 tekrar oynatma

İki renk arasında animasyon yapılarak sonsuza kadar maviye dönüşen yeşil bir arka plan.
Şekil 13. Arka plan rengi, iki değer arasında sonsuza kadar animasyonlu olarak değişiyor

Animasyonunuzun sürekli olarak tekrar etmesi için rememberInfiniteTransition ile infiniteRepeatable animationSpec kullanın. Nasıl ileri geri gideceğini 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 kompozisyona girdiğinde çalışır. Bir composable başlatıldığında animasyon başlatır. Animasyon durumunu değiştirmek için bunu kullanabilirsiniz. Lansmanda animasyonu başlatmak için Animatable ile animateTo yöntemini kullanma:

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

Sıralı animasyonlar oluşturma

Her birinin arasında animasyonlu yeşil oklar bulunan dört daire. Daireler birbiri ardına animasyonlu olarak gösteriliyor.
Şekil 14. Sıralı animasyonun nasıl ilerlediğini gösteren şema.

Sıralı veya eşzamanlı animasyonlar gerçekleştirmek için Animatable coroutine API'lerini kullanın. Animatable üzerinde animateTo işlevini art arda çağırmak, her animasyonun devam etmeden önce önceki animasyonların tamamlanmasını beklemesine neden olur . Bunun nedeni, bu işlevin 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))
}

Aynı anda animasyon oluşturma

Her birine doğru animasyonlu yeşil okların bulunduğu üç daire. Üç daire de aynı anda animasyonlu.
Şekil 15. Eşzamanlı animasyonların aynı anda nasıl ilerlediğini gösteren şema.

Eşzamanlı animasyonlar elde etmek için coroutine API'lerini (Animatable#animateTo() veya animate) ya da Transition API'yi kullanın. Bir coroutine 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 animasyonunu yönlendirmek için updateTransition API'yi kullanabilirsiniz. Aşağıdaki örnekte, durum değişikliğiyle kontrol edilen iki özellik (rect ve borderWidth) animasyonu 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

Oluşturma bölümündeki animasyonlar performans sorunlarına neden olabilir. Bunun nedeni, animasyonun doğasıdır: Hareket yanılsaması oluşturmak için ekrandaki piksellerin kare kare, hızlı bir şekilde hareket etmesi veya değişmesi.

Compose'un farklı aşamalarını (oluşturma, düzen ve çizme) 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 çizme aşamasında gerçekleşiyorsa genel olarak daha az iş yapması gerektiğinden düzen aşamasında animasyon çalıştırmanıza kıyasla varsayılan olarak daha iyi performans gösterir.

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şturmayı 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ığından 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

Oluşturma özelliği, çoğu animasyon için varsayılan olarak spring animasyonlarını kullanır. Yaylar veya fizik tabanlı animasyonlar daha doğal görünür. Sabit bir süre yerine nesnenin mevcut hızını dikkate aldıkları için kesintiye de uğrayabilirler. Varsayılanı geçersiz kılmak istiyorsanız daha önce gösterilen tüm animasyon API'leri, animasyonun nasıl çalışacağını özelleştirmek için bir animationSpec ayarlama özelliğine sahiptir. Bu sayede, animasyonun belirli bir süre boyunca yürütülmesini veya daha fazla sıçramalı olmasını sağlayabilirsiniz.

Farklı animationSpec seçeneklerinin özeti aşağıda 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 kelimesinin kısaltması): Süreye dayalı animasyon, Easing işleviyle iki değer arasında animasyon oluşturur.
  • keyframes: Animasyondaki belirli önemli noktalarda değerleri belirtme spesifikasyonu.
  • repeatable: RepeatMode ile belirtilen belirli sayıda çalışan süreye dayalı spesifikasyon.
  • infiniteRepeatable: Süreye dayalı ve süresiz olarak çalışan spesifikasyon.
  • snap: Herhangi bir animasyon olmadan anında son değere geçer.
Özellik kümesi olmayan ve özel Spring özellik kümesi olan iki animasyon gösteriliyor.
Şekil 16. Özellik kümesi ayarlanmamış ve özel yay özellik kümesi

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

Ek kaynaklar

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