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