Değere dayalı animasyonlar

animate*AsState ile tek bir değeri animasyonla değiştirme

animate*AsState işlevleri, tek bir değeri canlandırmak için Compose'daki en 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 kadar başlatır.

Aşağıda, bu API kullanılarak alfa animasyonu oluşturma örneği verilmiştir. Hedef değeri animateFloatAsState içine alarak alfa değeri artık sağlanan değerler (bu örnekte 1f veya 0.5f) arasında 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 işlemeniz gerekmediğini unutmayın. Arka planda, bir animasyon nesnesi (yani bir Animatable örneği) oluşturulur ve çağrı sitesinde hatırlanır. İlk hedef değer, bu nesnenin 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.

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

AnimationSpec sağlayarak animasyon özelliklerini özelleştirebilirsiniz. Daha fazla bilgi için AnimationSpec'e 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ünde olabilir. Çoğu durumda, tür güvenliğini sağlamak için özel bir enum türü kullanabilirsiniz. Örneğin:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition, Transition örneği 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'e 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ı olur. Bu, geçişin tamamlanıp tamamlanmadığına dair bir sinyal olarak kullanılabilir.

Bazen ilk hedef durumdan farklı bir başlangıç durumu kullanmak isteriz. Bunu yapmak için updateTransition ile MutableTransitionState'yi kullanabiliriz. Örneğin, kod kompozisyona girer girmez animasyonu başlatmamı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 createChildTransition kullanarak alt geçiş oluşturabilirsiniz. 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ında olur.

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
            }
        )
    }
}

AnimatedVisibility ve AnimatedContent ile geçiş kullanma

AnimatedVisibility ve AnimatedContent Transition'in uzantı işlevleri olarak kullanılabilir. targetState for Transition.AnimatedVisibility and Transition.AnimatedContent is derived from the Transition, and triggering enter/exit transitions as needed when the Transition's targetState has changed. Bu uzantı işlevleri, aksi takdirde AnimatedVisibility/AnimatedContent içinde dahili olacak tüm giriş/çıkış/boyut dönüştürme animasyonlarının Transition içine yerleştirilmesine olanak tanır. Bu uzantı işlevleriyle, AnimatedVisibility/AnimatedContent'nin durum değişikliği dışarıdan gözlemlenebilir. 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 başlıklı makaleleri inceleyin.

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, kullanıcı arayüzünüzle aynı composable'da geçiş animasyonları tanımlamak tamamen geçerli bir seçenektir. Ancak, bir dizi animasyonlu değer içeren karmaşık bir bileşen üzerinde çalışırken animasyon uygulamasını composable 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 "güncelleme" işlevi oluşturarak yapabilirsiniz. Geçiş uygulaması yeni ayrı işleve çıkarılabilir. Bu desen, animasyon mantığını merkezileştirmek veya karmaşık animasyonları yeniden kullanılabilir hale getirmek gerektiğinde faydalı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 örneğini rememberInfiniteTransition ile oluşturabilirsiniz. Alt animasyonlar animateColor, animatedFloat veya animatedValue ile eklenebilir. Animasyon özelliklerini belirtmek için bir infiniteRepeatable da 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 tüm üst düzey animasyon API'leri, düşük düzey animasyon API'lerinin temeli üzerine kurulmuştur.

animate*AsState işlevleri, anlık değer değişikliğini animasyon değeri olarak işleyen en basit API'lerdir. Tek bir değeri canlandırmak için kullanılan, eş yordama dayalı bir API olan Animatable tarafından desteklenir. updateTransition, birden fazla animasyonlu değeri yönetebilen ve bunları durum değişikliğine göre çalıştırabilen bir geçiş nesnesi oluşturur. rememberInfiniteTransition benzerdir ancak sonsuza kadar çalışmaya devam eden birden fazla animasyonu yönetebilen sonsuz bir geçiş oluşturur. Bu API'lerin tümü Animatable hariç composable'dır. Bu nedenle, bu animasyonlar composable dışında oluşturulabilir.

Bu API'lerin tümü daha temel olan Animation API'sine dayanmaktadır. Çoğu uygulama Animation ile doğrudan etkileşimde bulunmasa da Animation'nın bazı özelleştirme özellikleri daha üst düzey API'ler aracılığıyla kullanılabilir. AnimationVector ve AnimationSpec hakkında daha fazla bilgi için Animasyonları özelleştirme başlıklı makaleye bakın.

Çeşitli düşük seviyeli animasyon API&#39;leri arasındaki ilişkiyi gösteren şema

Animatable: Eş yordama dayalı tek değerli animasyon

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

Animatable'nın animateTo dahil birçok özelliği askıya alma işlevi olarak sunulur. Bu nedenle, uygun bir coroutine kapsamına alınmaları gerekir. Örneğin, LaunchedEffect composable'ı kullanarak yalnızca belirtilen anahtar değerin süresi boyunca geçerli olacak 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)
)

Yukarıdaki örnekte, Animatable öğesinin Color.Gray başlangıç değeriyle bir örneğini oluşturup hatırlıyoruz. Boole işaretinin değerine bağlı olarak ok, renk Color.Green veya Color.Red olarak animasyonlanır. Boole değerinde yapılan sonraki değişiklikler, animasyonu diğer renge başlatır. Değer değiştirildiğinde devam eden bir animasyon varsa animasyon iptal edilir ve yeni animasyon, geçerli anlık görüntü değeriyle geçerli hızda başlar.

Bu, önceki bölümde bahsedilen animate*AsState API'sini destekleyen animasyon uygulamasıdır. animate*AsState ile karşılaştırıldığında, Animatable doğrudan kullanıldığında çeşitli açılardan daha ayrıntılı kontrol elde edilebilir. İlk olarak, Animatable, ilk hedef değerinden farklı bir başlangıç değerine sahip olabilir. Örneğin, yukarıdaki kod örneğinde ilk başta gri bir kutu gösteriliyor. Bu kutu, hemen yeşil veya kırmızı renkte animasyon yapmaya başlıyor. İkinci olarak, Animatable, içerik değeri üzerinde daha fazla işlem (snapTo ve animateDecay) sağlar. snapTo geçerli değeri hemen hedef değere ayarlar. Bu, animasyonun tek doğru kaynağı olmadığı ve dokunma etkinlikleri gibi diğer durumlarla senkronize edilmesi gerektiğinde kullanışlıdır. animateDecay, verilen 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'ı destekler ancak TwoWayConverter sağlanarak herhangi bir veri türü kullanılabilir. Daha fazla bilgi için AnimationVector'a bakın.

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

Animation: Manuel olarak kontrol edilen animasyon

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

Animation yalnızca animasyonun süresini manuel olarak kontrol etmek için kullanılmalıdır. Animation durum bilgisi içermez ve yaşam döngüsü kavramı yoktur. Bu API, üst düzey API'lerin kullandığı bir 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 animasyon oynatma süresini kendiniz kontrol edebilirsiniz. Aşağıdaki örnekte, TargetAnimation öğesinin oynatma süresi, withFrameNanos tarafından sağlanan kare süresine göre manuel olarak kontrol ediliyor.

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'nın aksine, DecayAnimation için targetValue sağlanması gerekmez. Bunun yerine, targetValue, initialVelocity ve initialValue tarafından belirlenen başlangıç koşullarına ve sağlanan DecayAnimationSpec'e göre hesaplanır.

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