يحتوي Compose على العديد من آليات الرسوم المتحركة المضمنة، وقد يكون معرفة الطريقة التي يجب اختيارها أمرًا مربكًا. في ما يلي قائمة بحالات استخدام الرسوم المتحركة الشائعة. للحصول على مزيد من المعلومات التفصيلية حول المجموعة الكاملة من خيارات واجهة برمجة التطبيقات المختلفة المتاحة لك، اقرأ وثائق Compose Animation الكاملة.
إضافة تأثيرات حركية إلى السمات الشائعة القابلة للإنشاء
يوفر Compose واجهات برمجة تطبيقات ملائمة تتيح لك حل العديد من حالات استخدام الرسوم المتحركة الشائعة. يوضح هذا القسم كيف يمكنك تحريك الخصائص الشائعة لكائن قابل للإنشاء.
ظهور / اختفاء الصور المتحركة
يمكنك استخدام AnimatedVisibility
لإخفاء عنصر قابل للإنشاء أو إظهاره. يمكن للأطفال داخل
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()
مع إعداد اللون الذي يتضمن لقطة واحدة، ولكن عند إضافة تأثيرات حركية إلى لون بمرور الوقت، قد يؤدي ذلك إلى إعادة تركيب أكثر من اللازم.
للحصول على عدد لا نهائي من الصور المتحركة للخلفية، يمكنك الاطّلاع على تكرار قسم الصور المتحركة.
إنشاء صور متحركة بحجم عنصر قابل للإنشاء
تتيح لك علامة التبويب "إنشاء" إضافة تأثيرات حركية إلى حجم العناصر القابلة للإنشاء بعدة طرق مختلفة. استخدِم 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) ) }
تحريك مساحة متروكة لعنصر قابل للإنشاء
لإضافة تأثيرات متحركة إلى المساحة المتروكة في عنصر قابل للإنشاء، استخدِم السمة 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) ) }
تحريك لون النص
لتحريك لون النص، استخدِم دالة 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 في حدوث مشاكل في الأداء. ويرجع ذلك إلى طبيعة الرسوم المتحركة: تحريك وحدات البكسل أو تغييرها على الشاشة بسرعة، كل إطار تلو الآخر لخلق وهم الحركة.
جرّب المراحل المختلفة لإنشاء المحتوى: التركيب والتنسيق والرسم. إذا غيرت الرسوم المتحركة مرحلة التخطيط، فإنها تتطلب إعادة التخطيط وإعادة الرسم من خلال جميع العناصر القابلة للإنشاء المتأثرة. إذا حدثت الرسوم المتحركة في مرحلة الرسم، تكون أكثر أداءً بشكل افتراضي مما لو كنت تقوم بتشغيل الرسوم المتحركة في مرحلة التخطيط، حيث سيكون هناك جهد أقل يجب القيام به بشكل عام.
لضمان عمل تطبيقك بأقل قدر ممكن من المعلومات أثناء إنشاء الصور المتحركة، اختَر إصدار lambda
من Modifier
كلما أمكن ذلك. يؤدي هذا إلى تخطي إعادة الإنشاء وتنفيذ
الرسوم المتحركة خارج مرحلة الإنشاء، أو استخدام
Modifier.graphicsLayer{ }
، لأن هذا المعدِّل يتم تشغيله دائمًا في مرحلة الرسم. لمزيد من المعلومات حول هذا الأمر، اطّلِع على قسم تأجيل عمليات القراءة في مستندات الأداء.
تغيير توقيت الرسوم المتحركة
تستخدم ميزة "إنشاء" تلقائيًا صور الربيع المتحركة لمعظم الصور المتحركة. تبدو الينابيع أو الرسوم المتحركة
القائمة على الفيزياء طبيعية أكثر. كما أنها قابلة للمقاطعة لأنها تأخذ في الاعتبار
السرعة الحالية للكائن، بدلاً من وقت ثابت.
وإذا كنت تريد إلغاء الإعدادات التلقائية، يمكن لكلّ واجهات برمجة التطبيقات للصور المتحركة الموضّحة أعلاه
ضبط animationSpec
لتخصيص طريقة تشغيل الصورة المتحركة،
سواء كنت تريد تنفيذها خلال مدة معيّنة أو تنفيذ نمط ارتداد أكبر.
في ما يلي ملخّص لخيارات animationSpec
المختلفة:
spring
: صور متحركة تستند إلى الفيزياء، وهي الخطوة التلقائية لجميع الصور المتحركة. يمكنك تغيير الصلابة أو نسبة التخميد لتحقيق شكل ومظهر مختلف للرسوم المتحركة.tween
(اختصار يرمز إلى بين): تحرّك متحرك مستند إلى المدة بين قيمتين باستخدام دالةEasing
.keyframes
: مواصفات لتحديد القيم في نقاط رئيسية معيّنة في إحدى الصور المتحرّكة.repeatable
: مواصفات تستند إلى المدة ويتم تشغيلها لعدد معيّن من المرات، على أن يتم تحديدها من خلالRepeatMode
.infiniteRepeatable
: مواصفات تستند إلى المدة ويتم عرضها بشكل دائم.snap
: يتزامن فورًا مع القيمة النهائية بدون استخدام أي رسوم متحركة.
اقرأ الوثائق الكاملة للحصول على مزيد من المعلومات حول animationSpecs.
مراجع إضافية
لمزيد من الأمثلة حول الرسوم المتحركة الممتعة في Compose، ألق نظرة على ما يلي:
- 5 صور متحركة سريعة في ميزة Compose
- جعل قنديل البحر يتحرك في Compose
- تخصيص "
AnimatedContent
" في نافذة Compose - تسهيل استخدام دوال التخفيف في Compose