تتضمّن Compose العديد من آليات الرسوم المتحركة المضمّنة، وقد يكون من الصعب معرفة الآلية المناسبة للاستخدام. في ما يلي قائمة بحالات الاستخدام الشائعة للرسوم المتحركة. للحصول على مزيد من المعلومات التفصيلية حول المجموعة الكاملة من خيارات واجهة برمجة التطبيقات المختلفة المتاحة لك، يمكنك الاطّلاع على مستندات Compose Animation الكاملة.
تحريك الخصائص الشائعة القابلة للإنشاء
توفّر Compose واجهات برمجة تطبيقات ملائمة تتيح لك حل العديد من حالات الاستخدام الشائعة للرسوم المتحركة. يوضّح هذا القسم كيف يمكنك تحريك الخصائص الشائعة لعنصر قابل للإنشاء.
إنشاء صورة متحرّكة تظهر فيها العناصر وتختفي

استخدِم AnimatedVisibility
لإخفاء أو إظهار عنصر Composable. يمكن للأطفال الذين تتراوح أعمارهم بين AnimatedVisibility
وModifier.animateEnterExit()
استخدام الرسوم المتحركة الخاصة بهم عند الدخول إلى التطبيق أو الخروج منه.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
تسمح لك مَعلمات الدخول والخروج في AnimatedVisibility
بضبط طريقة عمل عنصر قابل للإنشاء عند ظهوره واختفائه. يمكنك الاطّلاع على المستندات
الكاملة للحصول على مزيد من المعلومات.
هناك خيار آخر لتحريك مستوى الشفافية لعنصر قابل للإنشاء وهو تحريك
قيمة ألفا بمرور الوقت باستخدام animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
ومع ذلك، عند تغيير قيمة قناة ألفا، يجب الانتباه إلى أنّ العنصر القابل للإنشاء يظل
في التركيبة ويواصل شغل المساحة التي تم وضعه فيها. وقد يؤدي ذلك إلى أن تواصل برامج قراءة الشاشة وآليات تسهيل الاستخدام الأخرى اعتبار العنصر معروضًا على الشاشة. من ناحية أخرى، تزيل AnimatedVisibility
العنصر من التركيب في النهاية.

تحريك لون الخلفية

val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
يكون هذا الخيار أكثر فعالية من استخدام Modifier.background()
.
يكون استخدام Modifier.background()
مقبولاً عند ضبط لون لمرة واحدة، ولكن عند تحريك لون بمرور الوقت، قد يؤدي ذلك إلى إعادة إنشاء التركيب أكثر من اللازم.
لتحريك لون الخلفية بلا حدود، راجِع قسم تكرار صورة متحركة.
تحريك حجم عنصر قابل للإنشاء

يتيح لك Compose تحريك حجم العناصر القابلة للإنشاء بعدة طرق مختلفة. استخدِم
animateContentSize()
للصور المتحركة بين تغييرات الحجم القابلة للإنشاء.
على سبيل المثال، إذا كان لديك مربّع يحتوي على نص يمكن توسيعه من سطر واحد إلى عدة أسطر، يمكنك استخدام Modifier.animateContentSize()
لتحقيق انتقال أكثر سلاسة:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
يمكنك أيضًا استخدام AnimatedContent
مع SizeTransform
لوصف كيفية إجراء تغييرات الحجم.
تحريك موضع العنصر القابل للإنشاء

لتحريك موضع عنصر قابل للإنشاء، استخدِم Modifier.offset{ }
مع animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
إذا أردت التأكّد من عدم رسم العناصر القابلة للإنشاء فوق أو تحت عناصر أخرى قابلة للإنشاء عند تحريك الموضع أو الحجم، استخدِم Modifier.layout{ }
. ينقل هذا المعدِّل تغييرات الحجم والموضع إلى العنصر الأصل، ما يؤثر بدوره في العناصر التابعة الأخرى.
على سبيل المثال، إذا كنت تنقل Box
ضمن Column
وكان يجب نقل العناصر الفرعية الأخرى عند نقل Box
، أدرِج معلومات الإزاحة مع Modifier.layout{ }
على النحو التالي:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }

Modifier.layout{ }
إضافة تأثيرات حركية إلى مساحة الحشو لعنصر قابل للإنشاء

لتحريك مساحة العرض الداخلية لعنصر قابل للإنشاء، استخدِم animateDpAsState
مع Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
إنشاء صورة متحركة لارتفاع عنصر قابل للإنشاء
لتحريك مستوى ارتفاع عنصر قابل للإنشاء، استخدِم animateDpAsState
مع Modifier.graphicsLayer{ }
. بالنسبة إلى التغييرات في الارتفاع التي تحدث لمرة واحدة، استخدِم
Modifier.shadow()
. إذا كنت تحرّك الظل، سيكون استخدام المعدِّل Modifier.graphicsLayer{ }
خيارًا أفضل من حيث الأداء.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
بدلاً من ذلك، استخدِم العنصر القابل للإنشاء Card
واضبط سمة الارتفاع على قيم مختلفة لكل حالة.
تحريك حجم النص أو ترجمته أو تدويره

عند تحريك حجم النص أو موضع النص أو تدويره، اضبط المَعلمة textMotion
في TextStyle
على TextMotion.Animated
. ويضمن ذلك انتقالات أكثر سلاسة بين الصور المتحركة للنصوص. استخدِم Modifier.graphicsLayer{ }
لترجمة النص أو تدويره أو تغيير حجمه.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
تحريك لون النص

لتحريك لون النص، استخدِم تعبير lambda color
في العنصر القابل للإنشاء BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
التبديل بين أنواع مختلفة من المحتوى

استخدِم AnimatedContent
للتحريك بين عناصر قابلة للإنشاء مختلفة، وإذا كنت تريد فقط الانتقال العادي بين العناصر القابلة للإنشاء، استخدِم Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
يمكن تخصيص AnimatedContent
لعرض العديد من الأنواع المختلفة من انتقالات الدخول والخروج. لمزيد من المعلومات، يمكنك الاطّلاع على المستندات حول
AnimatedContent
أو قراءة مشاركة المدونة هذه حول
AnimatedContent
.
عرض مؤثرات حركية أثناء الانتقال إلى وجهات مختلفة

لتحريك عمليات الانتقال بين العناصر القابلة للإنشاء عند استخدام العنصر navigation-compose، حدِّد enterTransition
وexitTransition
في عنصر قابل للإنشاء. يمكنك أيضًا ضبط الحركة التلقائية التي سيتم استخدامها لجميع وجهات المستوى الأعلى NavHost
:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
هناك العديد من أنواع انتقالات الدخول والخروج التي تطبّق تأثيرات مختلفة على المحتوى الوارد والصادر، يمكنك الاطّلاع على المستندات لمعرفة المزيد.
تكرار صورة متحركة

استخدِم rememberInfiniteTransition
مع infiniteRepeatable
animationSpec
لتكرار الصورة المتحركة بشكل مستمر. غيِّر RepeatModes
لتحديد كيفية الرجوع والتقدّم.
استخدِم finiteRepeatable
لتكرار عدد محدّد من المرات.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
بدء صورة متحركة عند تشغيل عنصر قابل للإنشاء
يتم تنفيذ LaunchedEffect
عندما يدخل عنصر قابل للإنشاء إلى التركيب. يبدأ هذا الإجراء حركة عند تشغيل عنصر قابل للإنشاء، ويمكنك استخدامه لتغيير حالة الحركة. استخدام Animatable
مع الطريقة animateTo
لبدء
الرسوم المتحركة عند التشغيل:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
إنشاء صور متحركة متسلسلة

استخدِم واجهات برمجة التطبيقات الخاصة بروتين Animatable
لتنفيذ الرسوم المتحركة التسلسلية أو المتزامنة. يؤدي استدعاء animateTo
على Animatable
واحدًا تلو الآخر إلى انتظار كل حركة حتى تنتهي الحركات السابقة قبل المتابعة .
والسبب في ذلك هو أنّها دالة تعليق.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
إنشاء صور متحركة متزامنة

استخدِم واجهات برمجة تطبيقات الروتينات الفرعية (Animatable#animateTo()
أو animate
) أو واجهة برمجة التطبيقات Transition
لتحقيق الرسوم المتحركة المتزامنة. إذا كنت تستخدم دوال تشغيل متعددة في سياق روتين فرعي، سيتم تشغيل الرسوم المتحركة في الوقت نفسه:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
يمكنك استخدام واجهة برمجة التطبيقات updateTransition
لاستخدام الحالة نفسها لتشغيل العديد من الرسوم المتحركة المختلفة للعناصر في الوقت نفسه. يعرض المثال أدناه حركة لسمتَين تتحكّم فيهما تغييرات الحالة، وهما rect
وborderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
تحسين أداء الرسوم المتحركة
يمكن أن تتسبّب الصور المتحرّكة في Compose في حدوث مشاكل في الأداء. ويرجع ذلك إلى طبيعة الرسوم المتحركة، وهي عبارة عن وحدات بكسل متحركة أو متغيرة على الشاشة بسرعة، إطارًا تلو الآخر، لخلق وهم الحركة.
ضَع في اعتبارك المراحل المختلفة في Compose: الإنشاء والتنسيق والرسم. إذا غيّرت الصورة المتحركة مرحلة التنسيق، يجب إعادة تنسيق جميع العناصر القابلة للإنشاء المتأثرة وإعادة رسمها. إذا كانت الصورة المتحركة تحدث في مرحلة الرسم، ستكون بشكل تلقائي أكثر فعالية من حيث الأداء مقارنةً بتشغيلها في مرحلة التخطيط، لأنّها ستتطلّب مجهودًا أقل بشكل عام.
لضمان تنفيذ تطبيقك لأقل قدر ممكن من العمليات أثناء عرض الرسوم المتحركة، اختَر إصدار lambda من Modifier
حيثما أمكن ذلك. يؤدي ذلك إلى تخطّي إعادة الإنشاء وتنفيذ الصورة المتحركة خارج مرحلة الإنشاء، وإلا استخدِم Modifier.graphicsLayer{ }
، لأنّ معدِّل العرض هذا يتم تنفيذه دائمًا في مرحلة الرسم. لمزيد من المعلومات حول هذا الموضوع، راجِع قسم تأجيل عمليات القراءة في مستندات الأداء.
تغيير توقيت الصورة المتحركة
يستخدم Compose تلقائيًا رسومات النابض المتحركة لمعظم الرسوم المتحركة. تكون الصور المتحركة المستندة إلى الفيزياء أكثر طبيعية. يمكن أيضًا إيقافها مؤقتًا لأنّها تأخذ في الاعتبار السرعة الحالية للعنصر بدلاً من وقت ثابت.
إذا أردت إلغاء الإعداد التلقائي، يمكن لجميع واجهات برمجة التطبيقات الخاصة بالرسوم المتحركة الموضّحة أعلاه ضبط animationSpec
لتخصيص طريقة تشغيل الرسوم المتحركة، سواء أردت تنفيذها خلال مدة زمنية معيّنة أو أن تكون أكثر حيوية.
في ما يلي ملخّص لخيارات animationSpec
المختلفة:
spring
: صورة متحركة مستنِدة إلى الفيزياء، وهي الإعداد التلقائي لجميع الصور المتحركة يمكنك تغيير قيمة stiffness أو dampingRatio للحصول على مظهر مختلف للحركة.tween
(اختصار بين): صورة متحركة مستندة إلى المدة، يتم تحريكها بين قيمتَين باستخدام الدالةEasing
.keyframes
: مواصفات لتحديد القيم عند نقاط رئيسية معيّنة في صورة متحركة.-
repeatable
: مواصفات مستندة إلى المدة يتم تنفيذها عددًا معيّنًا من المرات، يحدّدهRepeatMode
. -
infiniteRepeatable
: مواصفات مستندة إلى المدة يتم تنفيذها إلى الأبد. snap
: يتم الانتقال فورًا إلى القيمة النهائية بدون أي حركة.

يمكنك الاطّلاع على المستندات الكاملة للحصول على مزيد من المعلومات حول animationSpecs.
مراجع إضافية
للاطّلاع على المزيد من الأمثلة على الحركات الممتعة في Compose، يمكنك الاطّلاع على ما يلي:
- 5 صور متحركة سريعة في ميزة "إنشاء"
- تحريك سمكة الهلام في Compose
- تخصيص
AnimatedContent
في "الإنشاء" - التعرّف على دوال التباطؤ والتسارع في Compose