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

Oluşturma işleminde birçok yerleşik animasyon mekanizması bulunur ve hangisini seçeceğinizi bilmek zor olabilir. Aşağıda, animasyonun yaygın kullanım alanlarının bir listesi bulunmaktadır. Kullanabileceğiniz farklı API seçeneklerinin tamamı hakkında daha ayrıntılı bilgi için Animasyon Oluşturma dokümanlarının tamamını okuyun.

Yaygın composable mülklerini canlandırma

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

Animasyonun görünmesi / kaybolması

Kendini gösteren ve gizlenen yeşil composable
Şekil 1. Bir öğenin sütundaki görünümünü ve kaybolmasını animasyon haline getirme

Bir composable'ı gizlemek veya göstermek için AnimatedVisibility simgesini kullanın. AnimatedVisibility kapsamındaki ç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, composable'ın görünüp 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ırmak için bir başka seçenek de animateFloatAsState ile zaman içinde alfa animasyonu 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, alfa sürümünün değiştirilmesi, composable'ın bestede kalacağı ve yerleştirildiği alanı doldurmaya devam edeceği uyarısını da beraberinde getirir. Bu durum, ekran okuyucuların ve diğer erişilebilirlik mekanizmalarının ekrandaki öğeyi değerlendirmeye devam etmesine neden olabilir. Diğer yandan, AnimatedVisibility sonunda öğeyi besteden kaldırır.

Bir composable'ın alfasına animasyon ekleme
Şekil 2. composable'ın alfasına animasyon ekleme

Arka plan rengini canlandır

Zamanla arka plan renginin animasyon olarak değiştiği, renklerin birbirine dönüştüğü düzenlenebilen.
Ş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() kullanmaktan daha etkilidir. Modifier.background(), tek seferlik renk ayarı için kabul edilebilir ancak bir rengin zaman içinde animasyon oluşturması, gerekenden daha fazla yeniden kompozisyona neden olabilir.

Arka plan renginde sonsuz animasyon uygulamak için animasyon bölümünü tekrarlama konusuna bakın.

composable'ın boyutunu kullanarak

Boyutunun yumuşak bir şekilde değiştiğini gösteren yeşil composable.
Şekil 4. Küçük ve büyük boyutlar arasında sorunsuz şekilde animasyon oluşturulabilir

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

Örneğin, birden çok satıra genişletilebilen metin içeren bir kutunuz varsa daha yumuşak 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 bir SizeTransform ile birlikte AnimatedContent kullanabilirsiniz.

composable'ın konumunu canlandır

Aşağıya ve sağa doğru akıcı bir şekilde hareket eden yeşil composable
Şekil 5. Ofsete göre hareket eden composable

Bir composable'ın konumunu animasyon haline getirmek için Modifier.offset{ } ile birlikte animateIntOffsetAsState() öğesini 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 boyutu canlandırırken composable'ların diğer composable'ların üzerine ya da altına çizilmemesini istiyorsanız Modifier.layout{ } özelliğini kullanın. Bu değiştirici, boyut ve konum değişikliklerini üst öğeye uygular ve bu da diğer alt öğeleri etkiler.

Örneğin, bir Box öğesini Column içinde taşıyorsanız ve diğer alt öğelerin Box hareket ettiğinde 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 işaret ediyor, üçüncü kutu da kendini Y miktarında hareket ettirerek yanıt veriyor.
Şekil 6. Modifier.layout{ } ile animasyon oluşturma

composable'ın dolgusunu canlandırın

Tıklandığında küçültülen ve dolgulu animasyonlu yeşil composable
Şekil 7. Dolgu animasyonlarıyla oluşturulabilir

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

Şekil 8. Tıklandığında composable'ın yükseklik animasyonu

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() işlevini kullanın. Gölgeye animasyon uyguluyorsanız Modifier.graphicsLayer{ } değiştiricisini kullanmak daha etkili 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'ı kullanarak yükseklik özelliğini her durum için farklı değerlere ayarlayın.

Metin ölçeğini, çevirmeyi veya döndürmeyi canlandır

composable ifadesi
Şekil 9. İki boyut arasında yumuşak animasyon gösteren metin

Metnin ölçeğini, çevrilmesini veya döndürülmesini canlandırırken TextStyle üzerinde textMotion parametresini TextMotion.Animated olarak ayarlayın. Böylece metin animasyonları arasında daha yumuşak geçiş 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 rengine animasyon ekle

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

Metin rengine animasyon eklemek için BasicText composable'ında color lambda'yı 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

Yeşil ekran şunu diyor:
Şekil 11. Farklı composable'lar arasındaki değişiklikleri canlandırmak için Canlandırmalı İçerik'i kullanma (yavaşlatıldı)

Farklı composable'lar arasında animasyon yapmak için AnimatedContent kullanın. Yalnızca composable'lar arasında standart geçiş efekti 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ş/çıkış geçişi türünü gösterecek şekilde özelleştirilebilir. Daha fazla bilgi için AnimatedContent ile ilgili dokümanları okuyun veya AnimatedContent üzerindeki bu blog yayınını okuyun.

Farklı hedeflere giderken animasyon uygulama

Biri yeşil, diğeri "İnce", diğeri mavi renkli "Ayrıntı" adlı iki composable. composable, composable'ı açılış composable'ının üzerine kaydırarak animasyon yapıyor.
Şekil 12. Gezinme-kompozisyon işlevini 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 bir composable'da enterTransition ve exitTransition değerlerini belirtin. Üst düzeydeki tüm hedefler için kullanılacak varsayılan animasyonu da 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ş/çıkış geçişi türü vardır. Daha fazla bilgi için belgelere bakın.

Animasyonu tekrarlama

İki renk arasındaki animasyonla sonsuza kadar mavi arka plana dönüşen yeşil bir arka plan.
Şekil 13. İki değer arasında sonsuz animasyon gösteren arka plan rengi

Animasyonunuzu sürekli olarak tekrar etmek için infiniteRepeatable animationSpec ile rememberInfiniteTransition kullanın. Nasıl ileri veya geri gitmesi gerektiğini belirtmek için RepeatModes öğesini değiştirin.

Belirli bir sayıda tekrar etmek 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'ın açılışında animasyon başlatma

LaunchedEffect, bir composable besteye girdiğinde çalışır. Bir composable'ın başlatılmasında bir animasyon başlatır. Bunu, animasyonun durum değişikliğini sağlamak için kullanabilirsiniz. Başlatma sırasında 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

Yeşil okların arasında hareket eden, 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ş yordam API'lerini kullanın. Animatable öğesinde animateTo öğesinin birbiri ardına çağrılması, her animasyonun devam etmeden önce önceki animasyonların tamamlanmasını beklemesine neden olur . Bunun nedeni, bu fonksiyonun 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

Her birine doğru hareket eden yeşil oklara sahip üç daire, aynı anda hepsinin animasyonunu gösteriyor.
Şekil 15. Eşzamanlı animasyonların aynı anda nasıl ilerlediğini gösteren şema.

Eşzamanlı animasyonlar elde etmek için eş yordam API'lerini (Animatable#animateTo() veya animate) veya Transition API'yi kullanın. 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)
    }
}

Birçok farklı mülk animasyonunu aynı anda yönlendirmek amacıyla aynı durumu kullanmak için updateTransition API'yi kullanabilirsiniz. Aşağıdaki örnekte, durum değişikliği ile kontrol edilen iki özellik (rect ve borderWidth) canlandırılmaktadır:

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 edin

Oluşturma'daki animasyonlar performans sorunlarına neden olabilir. Bu, animasyonun doğasından kaynaklanır: Hareket illüzyonu yaratmak için ekrandaki pikselleri kare kare hızla taşımak veya değiştirmek.

Oluşturma işleminin 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üzenlenmesi ve yeniden çizilmesi gerekir. Animasyonunuz çizim aşamasında ortaya çıkarsa, animasyonu düzen aşamasında çalıştırdığınıza göre varsayılan olarak daha iyi performans gösterir, çünkü genel olarak yapılması gereken daha az iş olur.

Uygulamanızın animasyon oluştururken mümkün olduğunca az işlem yapmasını sağlamak için mümkün olduğunda bir Modifier öğesinin lambda sürümünü seçin. Bu işlem, yeniden oluşturma işlemini 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ırılacağı için Modifier.graphicsLayer{ } etiketini kullanın. Bu konu hakkında daha fazla bilgi edinmek için performans belgelerindeki okumaları erteleme bölümüne bakın.

Animasyon zamanlamasını değiştirme

Varsayılan olarak oluşturma işlemi, çoğu animasyon için ilkbahar animasyonlarını kullanır. Yaylar veya fiziğe dayalı animasyonlar daha doğal görünür. Bu makineler ayrıca sabit bir süre yerine cismin mevcut hızını hesaba kattığı için kesintiye neden olur. Varsayılan değeri geçersiz kılmak isterseniz yukarıda gösterilen tüm animasyon API'leri, animasyonun çalışma şeklini özelleştirmek için animationSpec ayarlama yeteneğine sahiptir. İstediğiniz zaman animasyonun belirli bir süre boyunca yürütülmesini veya daha fazla hareket etmesini sağlayabilirsiniz.

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

  • spring: Fizik kurallarını temel alan animasyon, tüm animasyonlar için varsayılan ayardır. Farklı bir animasyon görünümü ve tarzı elde etmek için sertliği veya sönümleme oranını değiştirebilirsiniz.
  • tween (arasının kısaltması): Süreye dayalı animasyon, Easing işleviyle iki değer arasında animasyon yapar.
  • keyframes: Bir animasyondaki belirli önemli noktalarda değerleri belirtme özelliği.
  • repeatable: Belirli sayıda çalışan, RepeatMode tarafından belirtilen süreye dayalı spesifikasyondur.
  • infiniteRepeatable: Sonsuza kadar çalışacak süreye dayalı spesifikasyon.
  • snap: Animasyon olmadan anında bitiş değerine tutturur.
Alternatif metninizi buraya yazın
Şekil 16. Spesifikasyon grubu olmayanlar ile Custom Spring teknik özellik kümesi arasındaki farklar

animationSpecs hakkında daha fazla bilgi edinmek 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: