Değere dayalı animasyonlar

Bu sayfada, Jetpack Compose'da değere dayalı animasyonların nasıl oluşturulacağı açıklanmaktadır. Mevcut ve hedef durumlarına göre değerleri canlandıran API'lere odaklanılmaktadır.

animate*AsState ile tek bir değeri canlandırma

animate*AsState işlevleri, tek bir değeri canlandırmak için Compose'daki basit animasyon API'leridir. Yalnızca hedef değeri (veya bitiş değerini) sağlarsınız ve API, animasyonu geçerli değerden belirtilen değere doğru başlatır.

Aşağıdaki örnekte, bu API kullanılarak alfa animasyonu yapılıyor. Hedef değeri animateFloatAsState içine alarak alfa değeri artık sağlanan değerler (bu örnekte 1f veya 0.5f) arasındaki bir animasyon değeri olur.

var enabled by remember { mutableStateOf(true) }

val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer { alpha = animatedAlpha }
        .background(Color.Red)
)

Herhangi bir animasyon sınıfının örneğini oluşturmanız veya kesintiyi yönetmeniz gerekmez. Arka planda, bir animasyon nesnesi (yani bir Animatable örneği) oluşturulur ve çağrı sitesinde hatırlanır. İlk hedef değer, başlangıç değeri olarak kullanılır. Bundan sonra, bu composable'a farklı bir hedef değer sağladığınız her seferde, bu değere yönelik bir animasyon otomatik olarak başlatılır. Halihazırda devam eden bir animasyon varsa animasyon, mevcut değerinden (ve hızından) başlar ve hedef değere doğru animasyon yapar. Animasyon sırasında bu composable yeniden oluşturulur ve her karede güncellenmiş bir animasyon değeri döndürür.

Varsayılan olarak Compose, Float, Color, Dp, Size, Offset, Rect, Int, IntOffset ve IntSize için animate*AsState işlevleri sağlar. Genel bir tür alan TwoWayConverter to animateValueAsState sağlayarak diğer veri türleri için destek ekleyebilirsiniz.

AnimationSpec sağlayarak animasyon özelliklerini özelleştirebilirsiniz. Daha fazla bilgi için AnimationSpec sayfasına bakın.

Geçişle birden fazla özelliği aynı anda canlandırma

Transition, alt öğeleri olarak bir veya daha fazla animasyonu yönetir ve bunları birden fazla durum arasında aynı anda çalıştırır.

Durumlar herhangi bir veri türü olabilir. Çoğu durumda, tür güvenliğini doğrulamak için özel bir enum tür kullanabilirsiniz. Örneğin:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition, Transition örneğini oluşturup hatırlar ve durumunu günceller.

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

Ardından, bu geçişte bir alt animasyon tanımlamak için animate* uzantı işlevlerinden birini kullanabilirsiniz. Durumların her biri için hedef değerleri belirtin. Bu animate* işlevler, geçiş durumu updateTransition ile güncellendiğinde animasyon sırasında her karede güncellenen bir animasyon değeri döndürür.

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

İsteğe bağlı olarak, geçiş durumu değişikliklerinin her bir kombinasyonu için farklı bir AnimationSpec belirtmek üzere transitionSpec parametresini iletebilirsiniz. Daha fazla bilgi için AnimationSpec sayfasına bakın.

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

Bir geçiş hedef duruma ulaştığında Transition.currentState, Transition.targetState ile aynıdır. Bu bilgiyi, geçişin tamamlanıp tamamlanmadığını belirlemek için sinyal olarak kullanabilirsiniz.

Bazen ilk hedef durumdan farklı bir başlangıç durumu kullanmak isteyebilirsiniz. Bu amaçla updateTransition ile MutableTransitionState ürününü kullanabilirsiniz. Örneğin, kod kompozisyona girer girmez animasyonu başlatmanıza olanak tanır.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

Birden fazla composable işlevin yer aldığı daha karmaşık bir geçiş için alt geçiş oluşturmak üzere createChildTransition kullanabilirsiniz. Bu teknik, karmaşık bir composable'daki birden fazla alt bileşen arasındaki endişeleri ayırmak için kullanışlıdır. Üst geçiş, alt geçişlerdeki tüm animasyon değerlerinin farkındadır.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

Geçişi AnimatedVisibility ve AnimatedContent ile kullanma

AnimatedVisibility ve AnimatedContent, Transition'in uzantı işlevleri olarak kullanılabilir. targetState, Transition.AnimatedVisibility ve Transition.AnimatedContent için Transition'den türetilir ve Transition'ün targetState değeri değiştiğinde gerektiği gibi giriş, çıkış ve sizeTransform animasyonlarını tetikler. Bu uzantı işlevleri, normalde AnimatedVisibility/AnimatedContent içinde yer alacak tüm giriş, çıkış ve sizeTransform animasyonlarını Transition içine taşımanıza olanak tanır. Bu uzantı işlevleriyle AnimatedVisibility/AnimatedContent'nin durum değişikliğini dışarıdan gözlemleyebilirsiniz. visible parametresi yerine, AnimatedVisibility'nin bu sürümü, üst geçişin hedef durumunu boole değerine dönüştüren bir lambda alır.

Ayrıntılar için AnimatedVisibility ve AnimatedContent sayfalarına bakın.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

Geçişi kapsülleme ve yeniden kullanılabilir hale getirme

Basit kullanım alanlarında, geçiş animasyonlarını kullanıcı arayüzünüzle aynı composable'da tanımlamak geçerli bir seçenektir. Ancak, bir dizi animasyonlu değer içeren karmaşık bir bileşen üzerinde çalışırken animasyon uygulamasını birleştirilebilir kullanıcı arayüzünden ayırmak isteyebilirsiniz.

Bunu, tüm animasyon değerlerini içeren bir sınıf ve bu sınıfın bir örneğini döndüren bir update işlevi oluşturarak yapabilirsiniz. Geçiş uygulamasını yeni ve ayrı bir işleve çıkarabilirsiniz. Bu kalıp, animasyon mantığını merkezileştirmeniz veya karmaşık animasyonları yeniden kullanılabilir hale getirmeniz gerektiğinde kullanışlıdır.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

rememberInfiniteTransition ile sonsuz döngüye giren bir animasyon oluşturma

InfiniteTransition, Transition gibi bir veya daha fazla alt animasyon içerir ancak animasyonlar kompozisyona girer girmez çalışmaya başlar ve kaldırılmadıkları sürece durmaz. InfiniteTransition öğesinin rememberInfiniteTransition ile bir örneğini oluşturabilir ve animateColor, animatedFloat veya animatedValue ile alt animasyonlar ekleyebilirsiniz. Animasyon özelliklerini belirtmek için bir infiniteRepeatable de belirtmeniz gerekir.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

Düşük düzeyli animasyon API'leri

Önceki bölümde bahsedilen üst düzey animasyon API'lerinin tümü, düşük düzeyli animasyon API'leri üzerine kurulmuştur.

animate*AsState işlevleri, anlık değer değişikliğini animasyon değeri olarak işleyen basit API'lerdir. Bu işlev, tek bir değeri canlandırmak için kullanılan, eş yordama dayalı bir API olan Animatable tarafından desteklenir.

updateTransition, birden fazla animasyon değerini yönetebilen ve durum değiştiğinde bunları çalıştırabilen bir geçiş nesnesi oluşturur. rememberInfiniteTransition benzerdir ancak süresiz olarak devam eden birden fazla animasyonu yönetebilen sonsuz bir geçiş oluşturur. Animatable dışındaki tüm bu API'ler composable'dır. Bu nedenle, bu animasyonları composable dışında oluşturabilirsiniz.

Bu API'lerin tümü daha temel olan Animation API'sine dayanmaktadır. Çoğu uygulama doğrudan Animation ile etkileşime girmese de bazı özelleştirme özelliklerine daha üst düzey API'ler aracılığıyla erişebilirsiniz. AnimationVector ve AnimationSpec hakkında daha fazla bilgi için Animasyonları özelleştirme başlıklı makaleyi inceleyin.

Düşük seviyeli animasyon API&#39;leri arasındaki ilişki
Şekil 1. Düşük seviyeli animasyon API'leri arasındaki ilişki.

Animatable: Coroutine tabanlı tek değerli animasyon

Animatable, animateTo kullanılarak değiştirilen değeri canlandırabilen bir değer tutucudur. Bu, animate*AsState uygulamasını destekleyen API'dir. Bu, tutarlı devamlılık ve karşılıklı dışlama sağlar. Yani değer değişikliği her zaman kesintisizdir ve Compose, devam eden tüm animasyonları iptal eder.

Animatable'daki animateTo dahil birçok özellik askıya alınmış işlevlerdir. Bu nedenle, bunları uygun bir coroutine kapsamına sarmalamanız gerekir. Örneğin, LaunchedEffect composable'ı kullanarak yalnızca belirtilen anahtar değerinin süresi için bir kapsam oluşturabilirsiniz.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

Önceki örnekte, Animatable örneğini Color.Gray başlangıç değeriyle oluşturup hatırlıyorsunuz. Boole işaretinin ok değerine bağlı olarak renk, Color.Green veya Color.Red olarak animasyon yapar. Boole değerinde yapılan sonraki değişiklikler, diğer renge yönelik bir animasyonu başlatır. Değer değiştiğinde bir animasyon devam ediyorsa Compose animasyonu iptal eder ve yeni animasyon, mevcut anlık görüntü değeriyle mevcut hızda başlar.

Bu Animatable API, önceki bölümde bahsedilen animate*AsState için temel uygulamadır. Animatable doğrudan kullanmak, çeşitli şekillerde daha ayrıntılı kontrol sağlar:

  • İlk olarak, Animatable ilk hedef değerinden farklı bir başlangıç değerine sahip olabilir. Örneğin, önceki kod örneğinde ilk başta gri bir kutu gösterilir ve bu kutu hemen yeşil veya kırmızı renkte animasyonla gösterilir.
  • İkincisi, Animatable, içerik değeri üzerinde daha fazla işlem sağlar. Özellikle snapTo ve animateDecay.
    • snapTo, mevcut değeri hemen hedef değere ayarlar. Bu, animasyon tek doğru kaynağı olmadığında ve dokunma etkinlikleri gibi diğer durumlarla senkronize edilmesi gerektiğinde kullanışlıdır.
    • animateDecay, belirtilen hızdan yavaşlayan bir animasyon başlatır. Bu, kaydırma davranışını uygulamak için kullanışlıdır.

Daha fazla bilgi için Hareket ve animasyon başlıklı makaleyi inceleyin.

Animatable varsayılan olarak Float ve Color'yi destekler ancak TwoWayConverter sağlayarak herhangi bir veri türünü kullanabilirsiniz. Daha fazla bilgi için AnimationVector sayfasına bakın.

AnimationSpec sağlayarak animasyon özelliklerini özelleştirebilirsiniz. Daha fazla bilgi için AnimationSpec sayfasına bakın.

Animation: Manuel olarak kontrol edilen animasyon

Animation, kullanılabilen en düşük düzeyli Animation API'sidir. Şu ana kadar gördüğümüz animasyonların çoğu Animation üzerine kuruludur. İki Animation alt türü vardır: TargetBasedAnimation ve DecayAnimation.

Animasyonun süresini manuel olarak kontrol etmek için yalnızca Animation simgesini kullanın. Animation durum bilgisi içermez ve yaşam döngüsü kavramı yoktur. Daha üst düzey API'ler için animasyon hesaplama motoru olarak işlev görür.

TargetBasedAnimation

Diğer API'ler çoğu kullanım alanını kapsar ancak TargetBasedAnimation doğrudan kullanıldığında animasyonun oynatma süresini kontrol edebilirsiniz. Aşağıdaki örnekte, TargetAnimation öğesinin oynatma süresini withFrameNanos tarafından sağlanan kare süresine göre manuel olarak kontrol ediyorsunuz.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

TargetBasedAnimation'dan farklı olarak DecayAnimation için targetValue sağlanması gerekmez. Bunun yerine, targetValue değerini initialVelocity ve initialValue tarafından belirlenen başlangıç koşullarına ve sağlanan DecayAnimationSpec değerine göre hesaplar.

Çürüme animasyonları, öğeleri durdurmak için genellikle fırlatma hareketinden sonra kullanılır. Animasyon hızı, initialVelocityVector ile belirlenen değerde başlar ve zamanla yavaşlar.