الدليل السريع للصور المتحركة في ميزة "الكتابة"

يحتوي Compose على العديد من آليات الرسوم المتحركة المضمنة، وقد يكون معرفة الطريقة التي يجب اختيارها أمرًا مربكًا. في ما يلي قائمة بحالات استخدام الرسوم المتحركة الشائعة. للحصول على مزيد من المعلومات التفصيلية حول المجموعة الكاملة من خيارات واجهة برمجة التطبيقات المختلفة المتاحة لك، اقرأ وثائق Compose Animation الكاملة.

إضافة تأثيرات حركية إلى السمات الشائعة القابلة للإنشاء

يوفر Compose واجهات برمجة تطبيقات ملائمة تتيح لك حل العديد من حالات استخدام الرسوم المتحركة الشائعة. يوضح هذا القسم كيف يمكنك تحريك الخصائص الشائعة لكائن قابل للإنشاء.

ظهور / اختفاء الصور المتحركة

عنصر قابل للإنشاء يظهر باللون الأخضر ويخفي نفسه
الشكل 1. إضافة تأثيرات حركية إلى مظهر عنصر واختفائه في عمود

يمكنك استخدام 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 العنصر من التركيبة في نهاية المطاف.

إنشاء صور متحركة ألفا في عنصر قابل للإنشاء
الشكل 2. إنشاء صورة متحركة ألفا لعنصر قابل للإنشاء

تحريك لون الخلفية

قابلة للإنشاء مع تغيّر لون الخلفية بمرور الوقت كرسم متحرك، حيث تتلاشى الألوان لتصبح في بعض الأحيان
الشكل 3. صورة متحركة للون الخلفية لعنصر

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

هذا الخيار أكثر أداءً من استخدام "Modifier.background()". يُسمح باستخدام السمة Modifier.background() مع إعداد اللون الذي يتضمن لقطة واحدة، ولكن عند إضافة تأثيرات حركية إلى لون بمرور الوقت، قد يؤدي ذلك إلى إعادة تركيب أكثر من اللازم.

للحصول على عدد لا نهائي من الصور المتحركة للخلفية، يمكنك الاطّلاع على تكرار قسم الصور المتحركة.

إنشاء صور متحركة بحجم عنصر قابل للإنشاء

أخضر قابل للإنشاء ويتغير بسلاسة في حجمه.
الشكل 4. قابلة للإنشاء بشكل متحرك بسلاسة بين حجم صغير وحجم أكبر

تتيح لك علامة التبويب "إنشاء" إضافة تأثيرات حركية إلى حجم العناصر القابلة للإنشاء بعدة طرق مختلفة. استخدِم 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 لوصف كيفية حدوث تغييرات في الحجم.

تحريك موضع العنصر القابل للإنشاء

قابل للإنشاء باللون الأخضر يتم تحريكه بسلاسة إلى الأسفل وإلى اليمين
الشكل 5. الحركة القابلة للإنشاء بإزاحة

لإضافة تأثير متحرك إلى موضع عنصر قابل للإنشاء، استخدِم السمة 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)
    )
}

مربعان يحتويان على المربع الثاني يتحرك موضع X وY، ويستجيب المربع الثالث بتحريك نفسه بمقدار Y أيضًا.
الشكل 6. أضِف تأثيرات متحركة باستخدام "Modifier.layout{ }"

تحريك مساحة متروكة لعنصر قابل للإنشاء

قابل للإنشاء يصغر حجمه ويكبر عند النقر، مع تحريك مساحة متروكة
الشكل 7. قابل للتركيب مع تحريكه للمساحة المتحرّكة

لإضافة تأثيرات متحركة إلى المساحة المتروكة في عنصر قابل للإنشاء، استخدِم السمة 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
        }
)

تحريك ارتفاع عنصر قابل للإنشاء

الشكل 8. الارتفاع في العنصر القابل للإنشاء متحرك عند النقر

لإضافة تأثيرات متحركة لارتفاع عنصر قابل للإنشاء، استخدِم السمة 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 القابلة للإنشاء، واضبط خاصية الارتفاع على قيم مختلفة لكل حالة.

تحريك مقياس النص أو الترجمة أو تدويره

نص قابل للإنشاء
الشكل 9. نص يتحرك بسلاسة بين حجمين

عند تحريك المقياس أو الترجمة أو تدوير النص، اضبط المَعلمة 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)
    )
}

تحريك لون النص

الكلمات
الشكل 10. مثال يعرض لون نص متحرك

لتحريك لون النص، استخدِم دالة 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
    },
    // ...
)

التبديل بين أنواع مختلفة من المحتوى

شاشة خضراء تقول
الشكل 11. استخدام محتوى الرسوم المتحركة لتحريك التغييرات بين العناصر المختلفة للإنشاء (تم الإبطاء)

استخدِم 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.

التحرّك أثناء التنقّل إلى وجهات مختلفة

عنصران قابلان للإنشاء، أحدهما باللون الأخضر يقولان "هبوط" والآخر أزرق يقول "التفاصيل" ويتحركان من خلال تمرير التفاصيل القابلة للإنشاء فوق الجزء القابل للإنشاء.
الشكل 12. إنشاء صور متحرّكة بين عناصر قابلة للإنشاء باستخدام Navigation-composer

لتحريك الانتقالات بين العناصر القابلة للإنشاء عند استخدام العنصر 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(
            // ...
        )
    }
}

هناك العديد من الأنواع المختلفة لانتقالات الدخول والخروج التي تطبّق تأثيرات مختلفة على المحتوى الوارد والصادر. يمكنك الاطّلاع على المستندات لمزيد من المعلومات.

تكرار صورة متحركة

خلفية خضراء تتحوّل إلى خلفية زرقاء بشكل غير محدود من خلال الصور المتحركة بين اللونَين
الشكل 13. لون في الخلفية يتحرك بين قيمتين، بشكل غير محدود

يمكنك استخدام 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
    }
)

إنشاء صور متحركة متسلسلة

أربع دوائر بأسهم خضراء تتحرك بين كل منها وتتحرك واحدة تلو الأخرى.
الشكل 14. مخطّط بياني يشير إلى كيفية تقدّم الصور المتحركة التسلسلية، واحدًا تلو الآخر

استخدِم واجهات برمجة تطبيقات الكورروتين 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))
}

إنشاء رسوم متحركة متزامنة

ثلاث دوائر تحتوي على أسهم خضراء تتحرك باتجاه كل دائرة، وتتحرك جميعها في الوقت نفسه.
الشكل 15. مخطّط بياني يشير إلى مستوى تقدّم الصور المتحركة المتزامنة، وكل ذلك في الوقت نفسه

استخدِم واجهات برمجة تطبيقات الكوروتين (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: يتزامن فورًا مع القيمة النهائية بدون استخدام أي رسوم متحركة.
اكتب النص البديل هنا.
الشكل 16. مقارنة عدم تحديد مواصفات بمجموعة مواصفات Spring المخصّصة

اقرأ الوثائق الكاملة للحصول على مزيد من المعلومات حول animationSpecs.

مراجع إضافية

لمزيد من الأمثلة حول الرسوم المتحركة الممتعة في Compose، ألق نظرة على ما يلي: