مثال انیمیشن پیشرفته: ژست ها

هنگام کار با رویدادها و انیمیشن های لمسی، در مقایسه با زمانی که به تنهایی با انیمیشن کار می کنیم، باید چندین چیز را در نظر بگیریم. اول از همه، ممکن است نیاز داشته باشیم که وقتی رویدادهای لمسی شروع می‌شوند، یک انیمیشن در حال انجام را قطع کنیم زیرا تعامل کاربر باید بالاترین اولویت را داشته باشد.

در مثال زیر، ما از Animatable برای نشان دادن موقعیت افست یک جزء دایره استفاده می کنیم. رویدادهای لمسی با اصلاح‌کننده pointerInput پردازش می‌شوند. هنگامی که یک رویداد ضربه ای جدید را تشخیص می دهیم، animateTo را صدا می کنیم تا مقدار افست را به موقعیت ضربه متحرک کنیم. یک رویداد ضربه ای نیز می تواند در طول انیمیشن اتفاق بیفتد، و در آن صورت، animateTo انیمیشن در حال انجام را قطع می کند و انیمیشن را در موقعیت هدف جدید شروع می کند در حالی که سرعت انیمیشن قطع شده را حفظ می کند.

@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())

الگوی رایج دیگر این است که ما باید مقادیر انیمیشن را با مقادیر ناشی از رویدادهای لمسی، مانند کشیدن، همگام کنیم. در مثال زیر، می‌بینیم که "swipe to dismiss" به‌عنوان یک Modifier (به‌جای استفاده از SwipeToDismiss ) اجرا شده است. افست افقی عنصر به عنوان Animatable نشان داده می شود. این API یک ویژگی مفید در انیمیشن ژست دارد. مقدار آن را می توان با رویدادهای لمسی و همچنین انیمیشن تغییر داد. هنگامی که یک رویداد لمسی دریافت می کنیم، Animatable با روش stop متوقف می کنیم تا هر انیمیشن در حال انجام رهگیری شود.

در طول یک رویداد کشیدن، از snapTo برای به روز رسانی مقدار Animatable با مقدار محاسبه شده از رویدادهای لمسی استفاده می کنیم. برای پرت کردن، Compose VelocityTracker را برای ثبت رویدادهای کشیدن و محاسبه سرعت فراهم می کند. سرعت را می توان به طور مستقیم به animateDecay برای انیمیشن پرتاب کرد. وقتی می خواهیم مقدار افست را به حالت اولیه برگردانیم، مقدار افست هدف 0f را با متد animateTo مشخص می کنیم.

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

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}