يتضمّن 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() مقبولاً لإعداد لون لمرة واحدة، ولكن عند
تحريك لون بمرور الوقت، قد يؤدي ذلك إلى إعادة إنشاء أكثر من
اللازم.
لتحريك لون الخلفية بشكل لا نهائي، يُرجى الاطّلاع على قسم تكرار الحركة.
تحريك حجم دالة مركّبة
تتيح لك 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 لتحديد كيفية الانتقال ذهابًا وإيابًا.
استخدِم repeatable لتكرار عدد محدّد من المرات.
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 coroutine لتنفيذ صور متحركة متسلسلة أو متزامنة. يؤدي استدعاء 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: صور متحركة مستنِدة إلى الفيزياء، وهي الإعداد التلقائي لجميع الصور المتحركة. يمكنك تغيير الصلابة أو نسبة التخميد للحصول على مظهر وأسلوب مختلفَين للحركة. -
tween(اختصار لـ between): هي صور متحركة مستندة إلى المدة، وتتحرك بين قيمتَين باستخدام دالةEasing. -
keyframes: مواصفات لتحديد القيم في نقاط رئيسية معيّنة في صورة متحركة repeatable: مواصفات مستندة إلى المدة يتم تشغيلها لعدد معيّن من المرات، يتم تحديده من خلالRepeatMode.-
infiniteRepeatable: مواصفات مستندة إلى المدة يتم تشغيلها إلى الأبد. -
snap: ينتقل زر التحكّم على الفور إلى القيمة النهائية بدون أي حركة.
لمزيد من المعلومات حول animationSpecs، يُرجى قراءة المستندات الكاملة.
مراجع إضافية
لمزيد من الأمثلة على الصور المتحركة الممتعة في Compose، يمكنك الاطّلاع على ما يلي:
- 5 صور متحركة سريعة في Compose
- تحريك قنديل البحر في Compose
- تخصيص
AnimatedContentفي Compose - التعرّف على دوال التخفيف في Compose