יש כמה דברים שאנחנו צריכים לקחת בחשבון כשאנחנו עובדים באירועי מגע ואנימציות, בהשוואה למצבים שבהם אנחנו עובדים רק אנימציות. קודם כל, יכול להיות שנצטרך לקטוע אנימציה מתמשכת כשאירועי מגע מתחילים, כי האינטראקציה של המשתמש צריכה להיות בעדיפות הגבוהה ביותר.
בדוגמה הבאה אנחנו משתמשים ב-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())
עוד דפוס נפוץ הוא שאנחנו צריכים לסנכרן ערכי אנימציה עם ערכים
שמגיע מאירועי מגע, כמו גרירה. בדוגמה הבאה, אנחנו רואים "החלקה אל
סגירה" הוטמע בתור Modifier
(במקום להשתמש
SwipeToDismiss
קומפוזבילי). ההיסט האופקי של הרכיב מיוצג
Animatable
לממשק ה-API הזה יש מאפיין שימושי באנימציה של תנועות. אפשר לשנות את הערך שלו באמצעות אירועי מגע וגם באמצעות האנימציה. כשאנחנו מקבלים אירוע של מגע, אנחנו מפסיקים את Animatable
באמצעות השיטה stop
כדי שתתבצע יירוט של כל אנימציה מתמשכת.
במהלך אירוע גרירה, אנחנו משתמשים ב-snapTo
כדי לעדכן את הערך של Animatable
בערך שמחושב מאירועי מגע. להחלקה, 'כתיבה' מספקת
VelocityTracker
כדי להקליט אירועי גרירה ולחשב מהירות. אפשר להעביר את המהירות ישירות אל animateDecay
עבור אנימציית ה-fling. כשאנחנו רוצים להחליק
את ערך ההיסט בחזרה למיקום המקורי, מציינים את קיזוז היעד
של 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) } }
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- אנימציות מבוססות-ערך
- גרירה, החלקה וזריחה
- הסבר על תנועות