Gelişmiş animasyon örneği: Hareketler

Yalnızca animasyonlarla çalışırken dikkate almamız gereken noktalara kıyasla, dokunma etkinlikleri ve animasyonlarla çalışırken dikkate almamız gereken birkaç nokta vardır. Öncelikle, kullanıcı etkileşiminin en yüksek önceliğe sahip olması gerektiğinden, dokunma etkinlikleri başladığında devam eden bir animasyonun kesintiye uğratılması gerekebilir.

Aşağıdaki örnekte, ofset konumunu göstermek için bir Animatable kullanıyoruz. oluşturabilirsiniz. Dokunma etkinlikleri pointerInput değiştiricisiyle işlenir. Yeni bir dokunma etkinliği algıladığımızda, dokunma konumuna göre ofset değerini animasyonlu olarak değiştirmek için animateTo işlevini çağırırız. Animasyon sırasında da dokunma etkinliği gerçekleşebilir. Bu durumda animateTo, devam eden animasyonu kesintiye uğratır ve kesintiye uğrayan animasyonun hızını koruyarak animasyonu yeni hedef konuma doğru başlatır.

@Composable
fun Gesture() {
    val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                coroutineScope {
                    while (true) {
                        // Detect a tap event and obtain its position.
                        awaitPointerEventScope {
                            val position = awaitFirstDown().position

                            launch {
                                // Animate to the tap position.
                                offset.animateTo(position)
                            }
                        }
                    }
                }
            }
    ) {
        Circle(modifier = Modifier.offset { offset.value.toIntOffset() })
    }
}

private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())

Sık karşılaşılan bir başka kalıp da, animasyon değerlerini dokunma etkinliklerinden gelen değerlerle (ör. sürükleme) senkronize etmemiz gerektiğidir. Aşağıdaki örnekte "kaydırmak için kaydır"ı kapat" bir Modifier olarak uygulanır ( SwipeToDismiss composable). Öğenin yatay ofseti Animatable olarak gösterilir. Bu API, hareket animasyonunda faydalı bir özelliğe sahiptir. Değeri, animasyonla birlikte dokunma etkinlikleriyle de değiştirilebilir. Bir dokunma etkinliğinde, Animatable işlemini stop yöntemiyle durdururuz. Böylece herhangi bir kesintiye uğrar.

Bir sürükleme etkinliği sırasında, Animatable değerini dokunma etkinliklerinden hesaplanan değerle güncellemek için snapTo değerini kullanırız. Compose, fling için Sürükleme etkinliklerini kaydetmek ve hızı hesaplamak için VelocityTracker. Hız, ekibinizin hızla sıçrama animasyonu için doğrudan animateDecay öğesine aktarılacaktır. Ofset değerini orijinal konuma geri kaydırmak istediğimizde animateTo yöntemiyle 0f değerinin hedef ofset değerini belirtiriz.

fun Modifier.swipeToDismiss(
    onDismissed: () -> Unit
): Modifier = composed {
    val offsetX = remember { Animatable(0f) }
    pointerInput(Unit) {
        // Used to calculate fling decay.
        val decay = splineBasedDecay<Float>(this)
        // Use suspend functions for touch events and the Animatable.
        coroutineScope {
            while (true) {
                val velocityTracker = VelocityTracker()
                // Stop any ongoing animation.
                offsetX.stop()
                awaitPointerEventScope {
                    // Detect a touch down event.
                    val pointerId = awaitFirstDown().id

                    horizontalDrag(pointerId) { change ->
                        // Update the animation value with touch events.
                        launch {
                            offsetX.snapTo(
                                offsetX.value + change.positionChange().x
                            )
                        }
                        velocityTracker.addPosition(
                            change.uptimeMillis,
                            change.position
                        )
                    }
                }
                // No longer receiving touch events. Prepare the animation.
                val velocity = velocityTracker.calculateVelocity().x
                val targetOffsetX = decay.calculateTargetValue(
                    offsetX.value,
                    velocity
                )
                // The animation stops when it reaches the bounds.
                offsetX.updateBounds(
                    lowerBound = -size.width.toFloat(),
                    upperBound = size.width.toFloat()
                )
                launch {
                    if (targetOffsetX.absoluteValue <= size.width) {
                        // Not enough velocity; Slide back.
                        offsetX.animateTo(
                            targetValue = 0f,
                            initialVelocity = velocity
                        )
                    } else {
                        // The element was swiped away.
                        offsetX.animateDecay(velocity, decay)
                        onDismissed()
                    }
                }
            }
        }
    }
        .offset { IntOffset(offsetX.value.roundToInt(), 0) }
}