Değere dayalı animasyonlar

animate*AsState ile tek bir değere animasyon ekleme

animate*AsState işlevleri, tek bir değeri animasyonlu hale getirmek 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 mevcut değerden belirtilen değere doğru başlatır.

Aşağıda, bu API'yi kullanarak alfa değerini animasyonlu hale getirme örneği verilmiştir. Hedef değeri animateFloatAsState içine sarmalayarak alfa değeri, sağlanan değerler (bu durumda 1f veya 0.5f) arasındaki bir animasyon değeri haline gelir.

var enabled by remember { mutableStateOf(true) }

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

Herhangi bir animasyon sınıfının örneğini oluşturmanıza veya kesintiyi yönetmenize gerek olmadığını unutmayın. Arka planda, ilk hedef değer başlangıç değeri olarak bir animasyon nesnesi (yani bir Animatable örneği) oluşturulur ve çağrı sitesinde hatırlanır. Bundan sonra, bu bileşene farklı bir hedef değer sağladığınızda o değere doğru otomatik olarak bir animasyon başlatılır. Devam eden bir animasyon varsa animasyon mevcut değerden (ve hızdan) başlar ve hedef değere doğru animasyon oluşturur. Animasyon sırasında bu kompozisyon yeniden oluşturulur ve her karede güncellenmiş bir animasyon değeri döndürür.

Oluşturma, Float, Color, Dp, Size, Offset, Rect, Int, IntOffset ve IntSize için hazır animate*AsState işlevleri sağlar. Genel bir tür alan TwoWayConverter to 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 bölümüne bakın.

Geçişle birden fazla mülkü aynı anda canlandırma

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

Durumlar herhangi bir veri türü olabilir. Çoğu durumda, aşağıdaki örnekte gösterildiği gibi tür güvenliğini sağlamak için özel bir enum türü kullanabilirsiniz:

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. Her bir eyaletin hedef değerlerini belirtin. Bu animate* işlevleri, 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şiklikleri kombinasyonlarının her biri için farklı bir AnimationSpec belirtmek üzere bir transitionSpec parametresi iletebilirsiniz. Daha fazla bilgi için AnimationSpec bölümüne 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ığını gösteren bir sinyal olarak kullanılabilir.

Bazen ilk hedef durumdan farklı bir başlangıç durumu olmasını isteriz. Bunu yapmak için updateTransitionMutableTransitionState ile birlikte 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şlevi içeren daha karmaşık bir geçiş için alt geçiş oluşturmak üzere createChildTransition kullanabilirsiniz. Bu teknik, karmaşık bir derlenebilir öğedeki birden fazla alt bileşen arasında endişeleri ayırmak için kullanışlıdır. Ana geçiş, alt geçişlerdeki tüm animasyon değerlerini bilir.

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'nin uzantı işlevleri olarak kullanılabilir. Transition.AnimatedVisibility ve Transition.AnimatedContent için targetState, Transition'dan türetilir ve Transition'ın targetState değeri değiştiğinde gerektiği gibi giriş/çıkış geçişlerini tetikler. Bu uzantı işlevleri, aksi takdirde AnimatedVisibility/AnimatedContent içinde 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'un durum değişikliği dışarıdan gözlemlenebilir. AnimatedVisibility'ın bu sürümünde, boole visible parametresi yerine, üst geçişin hedef durumunu boole değerine dönüştüren bir lambda işlevi kullanılır.

Ayrıntılar için AnimatedVisibility ve AnimatedContent özelliklerine 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şleri sarmalayarak yeniden kullanılabilir hale getirme

Basit kullanım alanları için geçiş animasyonlarını kullanıcı arayüzünüzle aynı kompozisyonda tanımlamak tamamen geçerli bir seçenektir. Ancak, animasyonlu değerlerin bulunduğu 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 barındıran 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ı, yeni ayrı işleve ayıklanabilir. Bu desen, animasyon mantığını merkezileştirmek veya karmaşık animasyonları yeniden kullanılabilir hale getirmek 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 tekrarlanan 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ıkça durmazlar. rememberInfiniteTransition ile InfiniteTransition örneği oluşturabilirsiniz. Alt animasyonlar animateColor, animatedFloat veya animatedValue ile eklenebilir. Animasyon özelliklerini belirtmek için 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üzey 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 inşa edilmiştir.

animate*AsState işlevleri, anlık değer değişimini animasyon değeri olarak gösteren en basit API'lerdir. Tek bir değeri animasyonlu hale getirmek için coroutine tabanlı 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 süresiz olarak çalışmaya devam eden birden fazla animasyonu yönetebilen sonsuz bir geçiş oluşturur. Bu API'lerin tümü, Animatable hariç olmak üzere derlenebilir. Bu, bu animasyonların kompozisyon dışında oluşturulabileceği anlamına gelir.

Bu API'lerin tümü daha temel Animation API'ye dayanır. Çoğu uygulama doğrudan Animation ile etkileşime girmese de Animation için özelleştirme özelliklerinden bazıları 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ı makaleyi inceleyin.

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

Animatable: Eş yordama tabanlı tek değer animasyonu

Animatable, animateTo aracılığıyla değiştikçe değeri animasyonlu olarak gösterebilen bir değer tutucusudur. Bu, animate*AsState'ün uygulanmasını destekleyen API'dir. Bu, tutarlı bir devamlılık ve karşılıklı münhasırlık sağlar. Yani değer değişikliği her zaman süreklidir ve devam eden animasyonlar iptal edilir.

animateTo dahil olmak üzere Animatable'ün birçok özelliği, askıya alma işlevi olarak sağlanır. Bu nedenle, uygun bir coroutine kapsamına alınmaları gerekir. Örneğin, yalnızca belirtilen anahtar değerin süresi boyunca geçerli olacak bir kapsam oluşturmak için LaunchedEffect bileşenini kullanabilirsiniz.

// 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, Color.Gray başlangıç değeri olan bir Animatable örneği oluşturup hatırlıyoruz. Boole işaretçisinin ok değerine bağlı olarak renk, Color.Green veya Color.Red olarak animasyonlu bir şekilde değişir. Boole değerinde yapılan sonraki değişiklikler, diğer renge animasyon başlatır. Değer değiştirilirken devam eden bir animasyon varsa animasyon iptal edilir ve yeni animasyon, mevcut anlık görüntü değerinden ve mevcut hızda başlar.

Bu, önceki bölümde bahsedilen animate*AsState API'yi destekleyen animasyon uygulamasıdır. animate*AsState ile karşılaştırıldığında, doğrudan Animatable kullanmak bize birçok açıdan daha ayrıntılı kontrol sağlar. Birincisi, 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österilir ve bu kutu hemen yeşile veya kırmızıya dönmeye başlar. İkinci olarak, Animatable içerik değeri üzerinde daha fazla işlem (snapTo ve animateDecay) sağlar. snapTo, geçerli değeri hedef değere hemen ayarlar. Bu, animasyon tek doğru kaynak 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, fırlatma davranışını uygulamak için kullanışlıdır. Daha fazla bilgi için İşaret ve animasyon bölümüne bakın.

Animatable, Float ve Color'yi destekler ancak TwoWayConverter sağlanarak her türlü veri türü kullanılabilir. Daha fazla bilgi için AnimationVector bölümüne bakın.

AnimationSpec sağlayarak animasyon özelliklerini özelleştirebilirsiniz. Daha fazla bilgi için AnimationSpec bölümüne bakın.

Animation: Manuel olarak kontrol edilen animasyon

Animation, mevcut en düşük düzeyli Animasyon API'sidir. Şimdiye kadar gördüğümüz animasyonların çoğu, animasyon üzerine inşa edilmiştir. İ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ına sahip değildir. Daha üst düzey API'lerin kullandığı bir animasyon hesaplama motoru görevi görür.

TargetBasedAnimation

Diğer API'ler çoğu kullanım alanını kapsar ancak TargetBasedAnimation'ü doğrudan kullanmak, animasyon oynatma süresini kendiniz kontrol etmenize olanak tanır. Aşağıdaki örnekte, TargetAnimation öğesinin oynatma süresi, withFrameNanos tarafından sağlanan kare süresine göre manuel olarak kontrol edilir.

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 aksine, DecayAnimation için targetValue sağlanmasına gerek yoktur. Bunun yerine, initialVelocity ve initialValue tarafından ayarlanan başlangıç koşullarına ve sağlanan DecayAnimationSpec'a göre targetValue değerini hesaplar.

Çökme animasyonları, öğeleri yavaşlatarak durdurmak için genellikle bir fırlatma hareketinden sonra kullanılır. Animasyon hızı, initialVelocityVector tarafından belirlenen değerde başlar ve zaman içinde yavaşlar.