توضّح هذه الصفحة كيفية إنشاء صور متحركة مستندة إلى القيم في Jetpack Compose، مع التركيز على واجهات برمجة التطبيقات التي تحرّك القيم استنادًا إلى حالاتها الحالية والمستهدَفة.
تحريك قيمة واحدة باستخدام 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
، ويتم تشغيل حركات الدخول والخروج وsizeTransform
حسب الحاجة عند تغيير targetState
الخاص بـ Transition
. تتيح لك دوال الإضافة هذه نقل جميع الصور المتحركة الخاصة بعمليات الدخول والخروج وsizeTransform
التي تكون عادةً داخل AnimatedVisibility
/AnimatedContent
إلى Transition
. باستخدام دوال الإضافة هذه، يمكنك مراقبة تغيير حالة 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
، يمكنك الوصول إلى بعض إمكانات التخصيص الخاصة بها من خلال واجهات برمجة التطبيقات ذات المستوى الأعلى. يمكنك الاطّلاع على تخصيص الرسوم المتحركة للحصول على مزيد من المعلومات حول AnimationVector
وAnimationSpec
.
Animatable
: صورة متحرّكة لقيمة واحدة مستندة إلى روتين فرعي
Animatable
هو عنصر نائب للقيمة يمكنه تحريك القيمة أثناء تغييرها
باستخدام animateTo
. هذه هي واجهة برمجة التطبيقات التي تدعم تنفيذ
animate*AsState
. ويضمن استمرارًا ثابتًا واستبعادًا متبادلاً، ما يعني أنّ تغيير القيمة يكون دائمًا مستمرًا، ويلغي Compose أي صورة متحركة قيد التنفيذ.
العديد من ميزات 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
. أي تغيير لاحق في القيمة المنطقية يؤدي إلى بدء حركة إلى اللون الآخر.
إذا كان هناك رسم متحرك قيد التقدّم عند تغيُّر القيمة، يلغي Compose الرسم المتحرك، ويبدأ الرسم المتحرك الجديد من قيمة اللقطة الحالية بالسرعة الحالية.
Animatable
API هو التنفيذ الأساسي لـ 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 غير مفعّلة
- تخصيص الصور المتحركة
- الصور المتحركة في Compose
- معدِّلات الصور المتحركة والعناصر القابلة للإنشاء