إضافة تأثيرات متحركة لقيمة واحدة باستخدام animate*AsState
دوال animate*AsState
هي أبسط واجهات برمجة التطبيقات للرسوم المتحركة في أداة "الإنشاء" لتحريك قيمة واحدة. ما عليك سوى تقديم القيمة المستهدَفة (أو القيمة النهائية)، وتبدأ واجهة برمجة التطبيقات عرض الصورة المتحركة من القيمة الحالية إلى القيمة المحدّدة.
في ما يلي مثال على إضافة تأثير متحرك إلى الصورة الشفافة باستخدام واجهة برمجة التطبيقات هذه. من خلال تضمين
القيمة المستهدَفة في animateFloatAsState
، تصبح قيمة ألفا الآن قيمة متحركة
بين القيم المقدَّمة (1f
أو 0.5f
في هذه الحالة).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
يُرجى العِلم أنّه ليس عليك إنشاء مثيل لأي فئة من فئات الصور المتحركة أو معالجة
المقاطعة. في الخلفية، سيتم إنشاء عنصر صورة متحركة (أي مثيل Animatable
) وتذكره في موقع الاستدعاء، مع استخدام قيمة الوجهة
الأولى كقيمة أولية. من الآن فصاعدًا، في أيّ وقت تقدّم فيه عنصرًا قابلاً للتجميع
بقيمة مستهدفة مختلفة، يبدأ عرض صورة متحركة تلقائيًا نحو تلك
القيمة. إذا كان هناك صورة متحركة قيد التنفيذ، تبدأ الصورة المتحركة من
قيمتها الحالية (وسرعتها) وتتحرك نحو القيمة المستهدَفة. أثناء التأثير المتحرك، تتم إعادة إنشاء هذا المكوّن القابل للتجميع ويعرض قيمة معدَّلة للتأثير المتحرك في كل لقطة.
توفّر ميزة "الإنشاء" تلقائيًا دوال 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
.
باستخدام وظائف الإضافات هذه، يمكن رصد
تغيير حالة AnimatedVisibility
/AnimatedContent
من الخارج. بدلاً من المَعلمة المنطقية visible
،
يأخذ هذا الإصدار من AnimatedVisibility
دالة لامبدا تحوّل الحالة المستهدَفة لانتقال
الرئيسي إلى قيمة منطقية.
اطّلِع على 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") } } } }
تجميع انتقال وجعله قابلاً لإعادة الاستخدام
بالنسبة إلى حالات الاستخدام البسيطة، يُعدّ تحديد الصور المتحركة للانتقالات في العنصر القابل للتجميع نفسه لواجهة المستخدم خيارًا صالحًا تمامًا. عند العمل على مكوّن معقّد يتضمّن عددًا من القيم المتحرّكة، قد تحتاج إلى فصل تنفيذ الحركة من واجهة المستخدم القابلة للتجميع.
ويمكنك إجراء ذلك عن طريق إنشاء فئة تحتوي على جميع قيم الصور المتحركة ودالة "update" تعرِض مثيلًا من تلك الفئة. يمكن استخراج تنفيذ الانتقال إلى الدالة المنفصلة الجديدة. يكون هذا النمط مفيداً عند الحاجة إلى تجميع منطق الصور المتحركة أو جعل الصور المتحركة المعقدة قابلة لإعادة الاستخدام.
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
لإنشاء
نطاق فقط لمدة قيمة المفتاح المحدّدة.
// 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
API
المذكور في القسم السابق. مقارنةً بـ animate*AsState
، يمنحنا استخدام
Animatable
مباشرةً إمكانية التحكّم بشكل أدق في عدة جوانب. أولاً،
يمكن أن يكون لـAnimatable
قيمة أولية مختلفة عن قيمته المستهدَفة الأولى.
على سبيل المثال، يعرض مثال الرمز البرمجي أعلاه مربّعًا رماديًا في البداية، والذي يبدأ على الفور
بالتحرّك إلى اللون الأخضر أو الأحمر. ثانيًا، توفّر Animatable
المزيد من
العمليات على قيمة المحتوى، وهي snapTo
وanimateDecay
. snapTo
يضبط القيمة الحالية على القيمة المستهدَفة على الفور. يكون ذلك مفيدًا عندما لا يكون المؤثر المتحرك هو المصدر الوحيد للحقيقة ويجب مزامنته مع حالات
أخرى، مثل أحداث اللمس. animateDecay
يبدأ صورة متحركة تتباطأ
من السرعة المحدّدة. ويُعدّ ذلك مفيدًا لتنفيذ سلوك الرمي. اطّلِع على
الإيماءات والرسوم المتحركة للحصول على مزيد من المعلومات.
يتيح Animatable
تلقائيًا استخدام Float
وColor
، ولكن يمكن استخدام أي نوع بيانات
من خلال توفير TwoWayConverter
. اطّلِع على
AnimationVector للحصول على مزيد من المعلومات.
يمكنك تخصيص مواصفات الحركة من خلال تقديم AnimationSpec
.
يمكنك الاطّلاع على AnimationSpec للحصول على مزيد من المعلومات.
Animation
: صورة متحرّكة يتم التحكّم فيها يدويًا
Animation
هو أدنى مستوى لواجهة برمجة التطبيقات Animation API متاح. إنّ العديد من الصور المتحركة
التي شاهدناها حتى الآن تستند إلى ميزة "الصور المتحركة". هناك نوعان فرعيان من 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
- مُعدِّلات الصور المتحركة والعناصر القابلة للتجميع