تحريك قيمة واحدة باستخدام 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
. تسمح دوال الإضافات هذه بتطبيق جميع الصور المتحركة
in/exit/sizeTransform التي كانت من الممكن أن تكون داخلية في
AnimatedVisibility
/AnimatedContent
ليتم نقلها إلى Transition
.
باستخدام وظائف الإضافة هذه، يمكن ملاحظة تغيير حالة AnimatedVisibility
/AnimatedContent
من الخارج. بدلاً من المَعلمة المنطقية visible
،
يأخذ هذا الإصدار من AnimatedVisibility
دالة لامبدا تحوّل الحالة المستهدَفة
لانتقال العنصر الرئيسي إلى قيمة منطقية.
راجع Animated visibility و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
- مُعدِّلات الصور المتحركة والعناصر القابلة للتجميع