Değere dayalı animasyonlar

animate*AsState ile tek bir değere animasyon ekleme

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

Aşağıda, bu API'nin kullanıldığı bir alfa animasyonu örneği verilmiştir. Hedef değeri animateFloatAsState içinde sarmaladığınızda alfa değeri artık sağlanan değerler (bu örnekte 1f veya 0.5f) arasında bir animasyon değeridir.

var enabled by remember { mutableStateOf(true) }

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

Herhangi bir animasyon sınıfının örneğini oluşturmanız veya kesintileri işlemeniz gerekmediğini unutmayın. Gelişmiş görünümde, ilk hedef değeri başlangıç değeri olacak şekilde bir animasyon nesnesi (yani bir Animatable örneği) oluşturulur ve çağrı sitesinde hatırlanır. Daha sonra, bu composable'a farklı bir hedef değer sağladığınızda, otomatik olarak bu değere doğru bir animasyon başlatılır. Yayın sırasında zaten bir animasyon varsa animasyon, geçerli değerinden (ve hızından) başlar ve hedef değere doğru canlandırılır. Animasyon sırasında bu composable yeniden derlenir ve her karede güncellenmiş bir animasyon değeri döndürür.

Oluşturulma özelliği, kullanıma hazır olarak Float, Color, Dp, Size, Offset, Rect, Int, IntOffset veIntSize için animate*AsState işlevleri sağlar. animateValueAsState için genel türde bir TwoWayConverter sağlayarak diğer veri türleri için kolayca destek ekleyebilirsiniz.

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

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

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

Durumlar herhangi bir veri türünde olabilir. Birçok durumda, aşağıdaki örnekte olduğu 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. Eyaletlerin her biri için hedef değerleri 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şikliği 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
    }
}

Hedef duruma bir geçiş geldiğinde Transition.currentState, Transition.targetState ile aynı olur. Bu, geçişin tamamlanıp tamamlanmadığının sinyali olarak kullanılabilir.

Bazen ilk hedef durumdan farklı bir başlangıç durumuna sahip olmak isteriz. Bunun için MutableTransitionState ile updateTransition kullanabiliriz. Örneğin, kod besteye 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 = updateTransition(currentState, label = "box state")
// ……

Birden fazla composable işlev içeren daha karmaşık bir geçiş için alt öğe geçişi oluşturmak üzere createChildTransition öğesini kullanabilirsiniz. Bu teknik, karmaşık bir composable'ın birden fazla alt bileşeni arasındaki kaygıları ayırmak için faydalıdır. Üst 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
            }
        )
    }
}

AnimatedVisibility ve AnimatedContent ile geçiş kullanın

AnimatedVisibility ve AnimatedContent, Transition öğesinin uzantı işlevleri olarak kullanılabilir. Transition.AnimatedVisibility ve Transition.AnimatedContent için targetState, Transition parametresinden türetilir ve Transition öğesinin targetState değeri değiştiğinde giriş/çıkış geçişlerini gerektiği şekilde tetikler. Bu uzantı işlevleri, normalde AnimatedVisibility/AnimatedContent içinde olacak tüm enter/exit/sizeTransform animasyonlarının Transition içine taşınmasına olanak tanır. Bu uzantı işlevleriyle AnimatedVisibility/AnimatedContent öğesinin durum değişikliği dışarıdan gözlemlenebilir. Boole visible parametresi yerine AnimatedVisibility bu sürümü, üst geçişin hedef durumunu boole'ye dönüştüren bir lambda kullanır.

Ayrıntılar için Animated visibility ve AnimatedContent konuları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),
    elevation = 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 yeniden kullanılabilir hale getirin

Basit kullanım örneklerinde, kullanıcı arayüzünüzle aynı composable'da geçiş animasyonlarını tanımlamak son derece 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 barındıran bir sınıf ve söz konusu sınıfın örneğini döndüren bir "güncelleme" işlevi oluşturarak yapabilirsiniz. Geçiş uygulaması, yeni ve ayrı bir işleve çıkarılabilir. Bu kalıp, animasyon mantığını merkezileştirme veya karmaşık animasyonları yeniden kullanılabilir hale getirme 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 sayıda tekrarlanan animasyon oluşturun

InfiniteTransition, Transition gibi bir veya daha fazla alt animasyon içerir ancak animasyonlar besteye girer girmez çalışmaya başlar ve kaldırılmadıkça durmaz. rememberInfiniteTransition ile InfiniteTransition örneği oluşturabilirsiniz. Alt animasyonlar animateColor, animatedFloat veya animatedValue ile eklenebilir. Animasyon özelliklerini belirtmek için bir infiniteRepeatable de belirtmeniz gerekir.

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

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

Alt düzey animasyon API'leri

Önceki bölümde bahsedilen üst düzey animasyon API'lerinin tümü, düşük seviyeli animasyon API'lerinin temeli üzerine inşa edilmiştir.

animate*AsState işlevleri, anında değer değişikliğini animasyon değeri olarak oluşturan en basit API'lerdir. Tek bir değeri canlandırmaya yönelik eş yordam tabanlı bir API olan Animatable tarafından desteklenir. updateTransition, birden fazla animasyon değerini 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 çok animasyonu yönetebilen sonsuz bir geçiş oluşturur. Bu API'lerin tümü composable'dır. Animatable dışında, bu animasyonlar kompozisyon dışında oluşturulabilir.

Bu API'lerin tümü, daha temel bir API olan Animation API'sini temel alır. Çoğu uygulama Animation ile doğrudan etkileşim kurmasa da Animation için bazı özelleştirme işlevleri, üst düzey API'ler aracılığıyla kullanılabilir. AnimationVector ve AnimationSpec hakkında daha fazla bilgi için Animasyonları özelleştirme'ye bakın.

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

Animatable: Eşliğe dayalı tek değer animasyonu

Animatable, animateTo aracılığıyla değiştirilen değeri canlandırabilen bir değer sahibidir. Bu, animate*AsState uygulamasını yedekleyen API'dir. Tutarlı bir devamı ve karşılıklı dışlayıcılığı sağlar, yani değer değişikliği her zaman sürekli olur ve devam eden tüm animasyonlar iptal edilir.

animateTo dahil olmak üzere Animatable, askıya alma işlevleri olarak sağlanır. Yani uygun bir eş yordam kapsamına alınması gerekir. Örneğin, yalnızca belirtilen anahtar değerinin süresi boyunca bir kapsam oluşturmak için LaunchedEffect composable'ı 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ğerine sahip bir Animatable örneği oluşturup hatırlıyoruz. Boole bayrağının değerine (ok) bağlı olarak, renk Color.Green veya Color.Red olarak canlandırılır. Boole değerinde daha sonra yapılacak herhangi bir değişiklik, animasyonu diğer renkle başlatır. Değer değiştirildiğinde devam eden bir animasyon varsa animasyon iptal edilir ve yeni animasyon geçerli hızda geçerli anlık görüntü değerinden başlar.

Bu, önceki bölümde bahsedilen animate*AsState API'yi yedekleyen animasyon uygulamasıdır. animate*AsState ile karşılaştırıldığında, Animatable doğrudan kullanıldığında birçok konuda bize daha hassas kontrol olanağı sunuluyor. Öncelikle, 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österilmektedir. Bu kutu hemen yeşil veya kırmızı animasyona başlar. İ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 yararlı olur. animateDecay, belirtilen hızdan yavaşlayan bir animasyon başlatır. Bu, kaçma davranışının uygulanmasında yararlıdır. Daha fazla bilgi için Hareket ve animasyon bölümüne bakın.

Animatable, hemen Float ve Color özelliklerini destekler. Ancak bir TwoWayConverter sağlanarak tüm veri türleri kullanılabilir. Daha fazla bilgi için bkz. AnimationVector.

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

Animation: Manuel olarak kontrol edilen animasyon

Animation, mevcut en düşük seviyeli Animasyon API'sidir. Şu ana kadar gördüğümüz animasyonların çoğu, Animasyon'un üzerine inşa edilmiştir. İki Animation alt türü vardır: TargetBasedAnimation ve DecayAnimation.

Animation, yalnızca animasyonun zamanını manuel olarak kontrol etmek için kullanılmalıdır. Animation durum bilgisizdir ve yaşam döngüsü kavramı yoktur. Daha ü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 oynatılma süresi, withFrameNanos tarafından sağlanan kare süresine göre manuel olarak kontrol edilmektedir.

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

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

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

DecayAnimation

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

Azaltma animasyonları, genellikle öğeleri bir duraklamaya kadar yavaşlatmak için bir sallama hareketinden sonra kullanılır. Animasyon hızı, initialVelocityVector tarafından ayarlanan değerden başlar ve zamanla yavaşlar.