Değere dayalı animasyonlar

animate*AsState ile tek bir değeri canlandırın

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 başlatır.

Aşağıda, bu API'yi kullanarak alfa testi uygulama örneği verilmiştir. Hedef değeri animateFloatAsState ile sarmaladığınızda alfa değeri, sağlanan değerler (bu örnekte 1f veya 0.5f) arasında artık bir animasyon değeri olur.

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 kesmeyi işlemeniz gerekmediğini unutmayın. Gelişmiş seçenekte, bir animasyon nesnesi (yani bir Animatable örneği) oluşturulur ve çağrı sitesinde hatırlanır. Burada ilk hedef değeri başlangıç değeri olarak kabul edilir. Bundan sonra, bu composable'a farklı bir hedef değer sağladığınızda, otomatik olarak bu değere yönelik bir animasyon başlatılır. Yayı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ş animasyon değeri döndürür.

Compose, Float, Color, Dp, Size, Offset, Rect, Int, IntOffset veIntSize için animate*AsState işlevleri sunar. Genel bir tür alan TwoWayConverter - animateValueAsState 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 öğesine bakın.

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

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

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

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

Daha sonra, 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şikliklerinin her biri kombinasyonu için farklı bir AnimationSpec belirtmek üzere transitionSpec parametresi iletebilirsiniz. Daha fazla bilgi için AnimationSpec öğesine 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 bitip bitmediğini belirtmek için bir sinyal olarak kullanılabilir.

Bazen ilk hedef durumdan farklı bir başlangıç durumunun olmasını isteriz. Bunu yapmak için MutableTransitionState ile updateTransition kullanabiliriz. Örneğin, kod kompozisyonu girer girmez animasyonu başlatmamıza olanak tanıyor.

// 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 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 faydalıdır. Üst geçiş, alt geçişlerdeki tüm animasyon değerlerini tanı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
            }
        )
    }
}

AnimatedVisibility ve AnimatedContent ile geçişi kullanın

AnimatedVisibility ve AnimatedContent, Transition öğesinin uzantı işlevleri olarak kullanılabilir. Transition.AnimatedVisibility ve Transition.AnimatedContent için targetState, Transition öğesinden 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 yer alacak tüm enter/exit/sizeTransform animasyonlarının Transition içine kaldırılması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, bu AnimatedVisibility sürümü, üst geçişin hedef durumunu boole'ye dönüştüren bir lambda alır.

Ayrıntılar için Animated visibility ve AnimatedContent'e göz atı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")
            }
        }
    }
}

Bir geçişi kapsama 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 son derece geçerli bir seçenektir. Ancak çok sayıda animasyonlu değere sahip 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 bu sınıfın bir örneğini döndüren bir "update" işlevi oluşturarak yapabilirsiniz. Geçiş uygulaması, yeni ve ayrı işleve çıkarılabilir. Bu kalıp, animasyon mantığının merkezileştirilmesi veya karmaşık animasyonların yeniden kullanılabilir hâle getirilmesi gerektiğinde yararlı olur.

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 tekrar eden bir 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ığı sürece durmaz. rememberInfiniteTransition ile InfiniteTransition örneği oluşturabilirsiniz. Alt animasyonlar animateColor, animatedFloat veya animatedValue ile eklenebilir. Animasyon özelliklerini belirtmek için bir infinite Repeatable da 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 tüm üst düzey animasyon API'leri, alt düzey animasyon API'lerinin temelleri üzerine kurulmuştur.

animate*AsState işlevleri, animasyon değeri olarak anında değer değişikliği oluşturan en basit API'lerdir. Tek bir değeri canlandırmak için eş hat 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. Animatable hariç tüm bu API'ler, bu animasyonların beste dışında oluşturulabileceği anlamına gelir.

Bu API'lerin tümü daha temel Animation API'ye dayanır. Çoğu uygulama Animation ile doğrudan etkileşime geçmeyecek olsa da Animation için özelleştirme özelliklerinden bazıları üst düzey API'ler aracılığıyla kullanılabilir. AnimationVector ve AnimationSpec hakkında daha fazla bilgi için Animasyonları özelleştirme bölümüne bakın.

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

Animatable: Koordine tabanlı tek değerli animasyon

Animatable, animateTo aracılığıyla değiştirilirken değeri animasyon oluşturabilen bir değer tutucudur. Bu, animate*AsState uygulamasını yedekleyen API'dir. Tutarlı bir devamlılık ve karşılıklı dışlama sağlar. Diğer bir deyişle, değer değişikliği her zaman sürekli olur ve devam eden animasyonlar iptal edilir.

Animatable ürününün animateTo dahil birçok özelliği, askıya alma işlevi olarak sunulur. Bu, projelerin uygun bir eş yordam kapsamına dahil edilmesi gerektiği anlamına gelir. Örneğin, LaunchedEffect composable'ı kullanarak yalnızca belirtilen anahtar değerinin süresi boyunca 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, başlangıç değeri Color.Gray olan bir Animatable örneği oluşturup hatırlıyoruz. ok boole işaretinin değerine bağlı olarak renk, Color.Green veya Color.Red değerine gösterilir. Boole değerinde daha sonra yapılan herhangi bir değişiklik, 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 hıza sahip 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 kullanmak, doğrudan bize çeşitli konularda daha ayrıntılı kontrol sağlar. Öncelikle, Animatable için ilk hedef değerinden farklı bir başlangıç değeri olabilir. Örneğin, yukarıdaki kod örneğinde ilk başta gri bir kutu gösterilmektedir. Kutu hemen yeşil veya kırmızıya dönüşmeye başlar. İkinci olarak Animatable, içerik değeri (snapTo ve animateDecay) üzerinde daha fazla işlem 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ılabilir. animateDecay, belirtilen hızdan yavaş olan bir animasyon başlatır. Bu, hızla kaydırma davranışını uygulamak için yararlıdır. Daha fazla bilgi için Hareket ve animasyon bölümüne bakın.

Animatable, Float ve Color özelliklerini hemen destekler ancak bir TwoWayConverter sağlanarak her tür veri kullanılabilir. Daha fazla bilgi için AnimationVector sayfasına bakın.

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

Animation: Manuel olarak kontrol edilen animasyon

Animation, kullanılabilen en düşük düzey Animasyon API'sidir. Şu ana kadar gördüğümüz animasyonların çoğu Animasyon'un üzerine inşa ediliyor. İ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 herhangi bir yaşam döngüsü kavramı yoktur. Üst düzey API'lerin kullandığı bir animasyon hesaplama motoru görevi görür.

TargetBasedAnimation

Diğer API'ler çoğu kullanım durumunu kapsar ancak TargetBasedAnimation kullandığınızda animasyon oynatma süresini doğrudan 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 edilir.

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 işlevinin aksine DecayAnimation için targetValue sağlanması gerekmez. Bunun yerine, targetValue değerini initialVelocity, initialValue ve sağlanan DecayAnimationSpec başlangıç koşullarını temel alarak hesaplar.

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