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