تحريك قيمة واحدة باستخدام animate*AsState
دوال animate*AsState
هي أبسط واجهات برمجة تطبيقات للرسوم المتحركة في Compose
لتحريك قيمة واحدة. ما عليك سوى تقديم القيمة المستهدَفة (أو القيمة النهائية)،
وتبدأ واجهة برمجة التطبيقات في عرض الصورة المتحركة من القيمة الحالية إلى القيمة المحدّدة.
في ما يلي مثال على تحريك قيمة ألفا باستخدام واجهة برمجة التطبيقات هذه. من خلال تضمين قيمة الهدف في animateFloatAsState
، تصبح قيمة ألفا الآن قيمة متحركة
بين القيمتين المقدَّمتين (1f
أو 0.5f
في هذه الحالة).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
يُرجى العِلم أنّه ليس عليك إنشاء مثيل لأي فئة من فئات الصور المتحركة أو التعامل مع الانقطاع. في الخلفية، سيتم إنشاء عنصر صورة متحركة (تحديدًا، مثيل Animatable
) وتذكّره في موقع الاستدعاء، مع تحديد القيمة الأولى المستهدَفة كقيمة أولية. بعد ذلك، في كل مرة تقدّم فيها قيمة مستهدَفة مختلفة لهذا العنصر القابل للإنشاء، سيتم تلقائيًا بدء رسم متحرك نحو تلك القيمة. إذا كانت هناك حركة قيد التنفيذ، ستبدأ الحركة من قيمتها الحالية (وسرعتها) وتتحرّك نحو القيمة المستهدَفة. أثناء
الرسم المتحرك، تتم إعادة إنشاء هذا العنصر القابل للإنشاء، ويعرض قيمة
معدَّلة للرسم المتحرك في كل إطار.
توفّر Compose تلقائيًا دوال animate*AsState
لكل من Float
وColor
وDp
وSize
وOffset
وRect
وInt
وIntOffset
وIntSize
. يمكنك بسهولة إضافة دعم لأنواع بيانات أخرى من خلال توفير
TwoWayConverter
إلى animateValueAsState
يأخذ نوعًا عامًا.
يمكنك تخصيص مواصفات الحركة من خلال تقديم AnimationSpec
.
يمكنك الاطّلاع على AnimationSpec لمزيد من المعلومات.
تحريك خصائص متعددة في الوقت نفسه باستخدام انتقال
يدير العنصر Transition
رسمًا متحركًا واحدًا أو أكثر كعناصر ثانوية له ويشغّلها
في الوقت نفسه بين حالات متعددة.
يمكن أن تكون الحالات من أي نوع بيانات. في كثير من الحالات، يمكنك استخدام نوع enum
مخصّص لضمان أمان النوع، كما في المثال التالي:
enum class BoxState { Collapsed, Expanded }
ينشئ updateTransition
مثيلاً من Transition
ويتذكّره ويعدّل حالته.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
يمكنك بعد ذلك استخدام إحدى دوال الإضافة animate*
لتحديد رسم متحرك فرعي في هذا الانتقال. حدِّد القيم المستهدَفة لكل حالة.
تعرض دالات animate*
قيمة صورة متحركة يتم تعديلها في كل إطار
أثناء الصورة المتحركة عند تعديل حالة الانتقال باستخدام
updateTransition
.
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 } }
يمكنك اختياريًا تمرير المَعلمة transitionSpec
لتحديد AnimationSpec
مختلف لكل مجموعة من مجموعات تغييرات حالة الانتقال. يمكنك الاطّلاع على
AnimationSpec لمزيد من المعلومات.
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 } }
بعد أن يصل الانتقال إلى الحالة المستهدَفة، ستكون قيمة Transition.currentState
هي نفسها قيمة Transition.targetState
. ويمكن استخدام ذلك كمؤشر لمعرفة ما إذا كان الانتقال قد انتهى.
في بعض الأحيان، نريد أن تكون الحالة الأولية مختلفة عن حالة الاستهداف الأولى. يمكننا استخدام updateTransition
مع MutableTransitionState
لتحقيق ذلك. على سبيل المثال، يسمح لنا هذا الإجراء ببدء الصورة المتحركة فور إدخال الرمز في التركيب.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
لإجراء انتقال أكثر تعقيدًا يتضمّن دوال متعددة قابلة للإنشاء، يمكنك استخدام createChildTransition
لإنشاء انتقال ثانوي. هذه الطريقة مفيدة لفصل الاهتمامات
بين عدة عناصر فرعية في عنصر قابل للإنشاء معقّد. سيكون الانتقال الرئيسي على دراية بجميع قيم الصور المتحركة في عمليات الانتقال الفرعية.
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
وAnimatedContent
يتوفّر كل من AnimatedVisibility
وAnimatedContent
كوظائف إضافية في Transition
. يتم اشتقاق targetState
الخاص بـ Transition.AnimatedVisibility
وTransition.AnimatedContent
من Transition
، ويتم تشغيل انتقالات الدخول/الخروج حسب الحاجة عند تغيير targetState
الخاص بـ Transition
. تسمح دوال الإضافة هذه بنقل جميع الرسوم المتحركة الخاصة بالدخول/الخروج/تغيير الحجم التي تكون عادةً داخلية في AnimatedVisibility
/AnimatedContent
إلى Transition
.
باستخدام دوال الإضافة هذه، يمكن مراقبة تغيير حالة AnimatedVisibility
/AnimatedContent
من الخارج. بدلاً من المَعلمة المنطقية visible
،
يستخدِم هذا الإصدار من AnimatedVisibility
تعبير lambda يحوّل حالة الانتقال الرئيسية إلى قيمة منطقية.
يمكنك الاطّلاع على AnimatedVisibility وAnimatedContent للحصول على التفاصيل.
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), shadowElevation = 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") } } } }
تغليف عملية انتقال وجعلها قابلة لإعادة الاستخدام
بالنسبة إلى حالات الاستخدام البسيطة، يكون تحديد الحركات الانتقالية في العنصر القابل للإنشاء نفسه الذي تتضمّنه واجهة المستخدم خيارًا صالحًا تمامًا. عند العمل على مكوّن معقّد يتضمّن عددًا من القيم المتحرّكة، قد تحتاج إلى فصل عملية تنفيذ الحركة عن واجهة المستخدم القابلة للإنشاء.
يمكنك إجراء ذلك من خلال إنشاء فئة تتضمّن جميع قيم الرسوم المتحركة ودالة "تعديل" تعرض مثيلاً لهذه الفئة. يمكن استخراج عملية تنفيذ الانتقال إلى الدالة الجديدة المنفصلة. يكون هذا النمط مفيدًا عند الحاجة إلى مركزية منطق الصورة المتحركة، أو جعل الصور المتحركة المعقدة قابلة لإعادة الاستخدام.
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
يحتوي InfiniteTransition
على صورة متحركة واحدة أو أكثر، مثل Transition
، ولكن تبدأ الصور المتحركة في العمل فور دخولها إلى التركيبة ولا تتوقف إلا إذا تمت إزالتها. يمكنك إنشاء مثيل من InfiniteTransition
باستخدام rememberInfiniteTransition
. يمكن إضافة صور متحركة للأطفال باستخدام animateColor
أو animatedFloat
أو animatedValue
. عليك أيضًا تحديد
infiniteRepeatable لتحديد
مواصفات الحركة.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
واجهات برمجة التطبيقات للصور المتحركة ذات المستوى المنخفض
تم إنشاء جميع واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة ذات المستوى العالي المذكورة في القسم السابق استنادًا إلى واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة ذات المستوى المنخفض.
تُعدّ دوال animate*AsState
أبسط واجهات برمجة التطبيقات، وهي تعرض تغييرًا فوريًا في القيمة كقيمة صورة متحركة. تستند هذه السمة إلى Animatable
، وهي واجهة برمجة تطبيقات مستندة إلى إجراءات فرعية لتفعيل حركة قيمة واحدة. تنشئ updateTransition
عنصر انتقال يمكنه إدارة قيم متحركة متعددة وتشغيلها استنادًا إلى تغيير في الحالة. rememberInfiniteTransition
مشابهة، ولكنّها تنشئ انتقالاً لا نهائيًا يمكنه إدارة حركات متعدّدة تستمر في العمل إلى أجل غير مسمى. جميع واجهات برمجة التطبيقات هذه هي عناصر قابلة للإنشاء باستثناء Animatable
، ما يعني أنّه يمكن إنشاء هذه الرسوم المتحركة خارج عملية الإنشاء.
تستند جميع واجهات برمجة التطبيقات هذه إلى واجهة برمجة التطبيقات الأساسية Animation
. على الرغم من أنّ معظم التطبيقات لن تتفاعل مباشرةً مع Animation
، تتوفّر بعض إمكانات التخصيص في Animation
من خلال واجهات برمجة التطبيقات ذات المستوى الأعلى. يمكنك الاطّلاع على تخصيص الرسوم المتحركة للحصول على مزيد من المعلومات حول AnimationVector
وAnimationSpec
.
Animatable
: صورة متحرّكة لقيمة واحدة مستندة إلى الكوروتين
Animatable
هو عنصر نائب للقيمة يمكنه تحريك القيمة أثناء تغييرها من خلال animateTo
. هذه هي واجهة برمجة التطبيقات التي تدعم تنفيذ animate*AsState
.
ويضمن ذلك استمرارًا متسقًا واستبعادًا متبادلاً، ما يعني أنّ تغيير القيمة يكون دائمًا مستمرًا وسيتم إلغاء أي رسوم متحركة قيد التنفيذ.
يتم توفير العديد من ميزات Animatable
، بما في ذلك animateTo
، كدوال معلّقة. وهذا يعني أنّه يجب تضمينها في نطاق مناسب لروتين فرعي. على سبيل المثال، يمكنك استخدام LaunchedEffect
composable لإنشاء نطاق
لمدة قيمة المفتاح المحدّدة فقط.
// 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) )
في المثال أعلاه، ننشئ مثيلاً من Animatable
ونحفظه بالقيمة الأولية Color.Gray
. استنادًا إلى قيمة العلامة المنطقية ok
، يتم تحريك اللون إما إلى Color.Green
أو Color.Red
. ويؤدي أي تغيير لاحق في القيمة المنطقية إلى بدء الحركة إلى اللون الآخر. إذا كانت هناك صورة متحركة قيد التنفيذ عند تغيير القيمة، يتم إلغاء الصورة المتحركة، وتبدأ الصورة المتحركة الجديدة من قيمة اللقطة الحالية بالسرعة الحالية.
هذا هو تنفيذ الصورة المتحركة الذي يوفّر الدعم لواجهة برمجة التطبيقات animate*AsState
المذكورة في القسم السابق. مقارنةً بـ animate*AsState
، يتيح لنا استخدام Animatable
مباشرةً التحكّم بشكل أكثر دقة في عدّة جوانب. أولاً، يمكن أن يكون لقيمة Animatable
قيمة أولية مختلفة عن قيمة الاستهداف الأولى.
على سبيل المثال، يعرض نموذج الرمز البرمجي أعلاه مربّعًا رماديًا في البداية، ثم يبدأ على الفور في عرض حركة إما باللون الأخضر أو الأحمر. ثانيًا، يوفّر Animatable
المزيد من العمليات على قيمة المحتوى، وتحديدًا snapTo
وanimateDecay
. يؤدي snapTo
إلى ضبط القيمة الحالية على القيمة المستهدَفة على الفور. ويكون ذلك مفيدًا عندما لا يكون الرسم المتحرّك هو المصدر الوحيد للحقيقة ويجب مزامنته مع حالات أخرى، مثل أحداث اللمس. يبدأ animateDecay
صورة متحركة تبطئ
من السرعة المحدّدة. ويكون ذلك مفيدًا لتنفيذ سلوك التمرير السريع. يمكنك الاطّلاع على الإيماءات والرسوم المتحركة لمزيد من المعلومات.
يتوافق Animatable
مع Float
وColor
بشكلٍ تلقائي، ولكن يمكن استخدام أي نوع من البيانات من خلال توفير TwoWayConverter
. يمكنك الاطّلاع على AnimationVector للحصول على مزيد من المعلومات.
يمكنك تخصيص مواصفات الحركة من خلال تقديم AnimationSpec
.
يمكنك الاطّلاع على AnimationSpec لمزيد من المعلومات.
Animation
: صورة متحركة يتم التحكّم فيها يدويًا
Animation
هو أدنى مستوى لواجهة برمجة تطبيقات الرسوم المتحركة المتاحة. تستند العديد من الصور المتحركة التي رأيناها حتى الآن إلى Animation. هناك نوعان فرعيان من Animation
:
TargetBasedAnimation
وDecayAnimation
.
يجب استخدام Animation
للتحكّم يدويًا في وقت عرض الصورة المتحركة فقط.
Animation
عديم الحالة، وليس لديه أي مفهوم لدورة الحياة. وهي تعمل كمحرّك لحساب الرسوم المتحركة تستخدمه واجهات برمجة التطبيقات ذات المستوى الأعلى.
TargetBasedAnimation
تغطي واجهات برمجة التطبيقات الأخرى معظم حالات الاستخدام، ولكن استخدام TargetBasedAnimation
مباشرةً يتيح لك التحكّم في وقت تشغيل الرسوم المتحركة بنفسك. في المثال أدناه، يتم التحكّم يدويًا في وقت تشغيل TargetAnimation
استنادًا إلى وقت الإطار الذي يوفّره withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
على عكس TargetBasedAnimation
،
DecayAnimation
لا يتطلّب توفير targetValue
. بدلاً من ذلك، يتم احتساب targetValue
استنادًا إلى الشروط الأولية التي يحدّدها initialVelocity
وinitialValue
وDecayAnimationSpec
المقدَّمة.
غالبًا ما تُستخدم الحركات المتحلّلة بعد إيماءة النقر السريع لإبطاء حركة العناصر إلى أن تتوقف. تبدأ سرعة الصورة المتحركة بالقيمة التي يحدّدها initialVelocityVector
وتتباطأ بمرور الوقت.
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة
- تخصيص الصور المتحركة {:#customize-animations}
- الصور المتحركة في Compose
- معدِّلات الصور المتحركة والعناصر القابلة للإنشاء