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