উন্নত অ্যানিমেশন উদাহরণ: অঙ্গভঙ্গি

শুধুমাত্র অ্যানিমেশনের সাথে কাজ করার তুলনায়, টাচ ইভেন্ট এবং অ্যানিমেশন নিয়ে কাজ করার সময় আমাদের বেশ কিছু বিষয় বিবেচনা করতে হয়। প্রথমত, টাচ ইভেন্ট শুরু হলে আমাদের চলমান অ্যানিমেশন বন্ধ করতে হতে পারে কারণ ব্যবহারকারীর ইন্টারঅ্যাকশনকে সর্বোচ্চ অগ্রাধিকার দেওয়া উচিত।

নিচের উদাহরণে, আমরা একটি বৃত্ত উপাদানের অফসেট অবস্থান উপস্থাপন করার জন্য একটি Animatable ব্যবহার করি। টাচ ইভেন্টগুলি pointerInput modifier দিয়ে প্রক্রিয়া করা হয়। যখন আমরা একটি নতুন ট্যাপ ইভেন্ট সনাক্ত করি, তখন অফসেট মানটিকে ট্যাপ অবস্থানে অ্যানিমেট করার জন্য আমরা 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 composable ব্যবহার করার পরিবর্তে)। এলিমেন্টের অনুভূমিক অফসেটটি Animatable হিসাবে উপস্থাপিত হয়। এই API-এর একটি বৈশিষ্ট্য রয়েছে যা অঙ্গভঙ্গি অ্যানিমেশনে কার্যকর। এর মান টাচ ইভেন্টের পাশাপাশি অ্যানিমেশন দ্বারাও পরিবর্তন করা যেতে পারে। যখন আমরা একটি টাচ ডাউন ইভেন্ট পাই, তখন আমরা stop পদ্ধতি দ্বারা Animatable বন্ধ করি যাতে যেকোনো চলমান অ্যানিমেশন আটকে যায়।

ড্র্যাগ ইভেন্টের সময়, আমরা snapTo ব্যবহার করে টাচ ইভেন্ট থেকে গণনা করা মানের সাথে Animatable মান আপডেট করি। ফ্লিংয়ের জন্য, কম্পোজ ড্র্যাগ ইভেন্ট রেকর্ড করতে এবং বেগ গণনা করতে VelocityTracker প্রদান করে। ফ্লিং অ্যানিমেশনের জন্য বেগ সরাসরি animateDecay তে ফিড করা যেতে পারে। যখন আমরা অফসেট মানটিকে মূল অবস্থানে ফিরিয়ে আনতে চাই, তখন আমরা animateTo পদ্ধতি ব্যবহার করে 0f এর লক্ষ্য অফসেট মান নির্দিষ্ট করি।

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

{% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %} {% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %}