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

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

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

توفّر ميزة 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. مثال يعرض لون نص متحرك

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

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

شاشة خضراء تقول
الشكل 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-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 API لاستخدام الحالة نفسها لجذب العديد من الصور المتحركة المختلفة للسمات في الوقت نفسه. يحرّك المثال أدناه خاصيتَين يتحكّم فيهما تغيير في الحالة، وهما 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{ } في الحالات الأخرى، لأنّ أداة التعديل هذه تعمل دائمًا في مرحلة الرسم. لمزيد من المعلومات عن ذلك، راجِع قسم قراءات التأجيل في مستندات الأداء.

تغيير توقيت الرسوم المتحركة

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

في ما يلي ملخّص لخيارات "animationSpec" المختلفة:

  • spring: صورة متحركة مستندة إلى الفيزياء، وهي الخيار التلقائي لكل الصور المتحركة. يمكنك تغيير مستوى الصلابة أو التخميد للحصول على مظهر وشكل مختلف للصور المتحركة.
  • tween (اختصار للمصطلح بين): صورة متحركة مستنِدة إلى المدة، وهي تتحرك بين قيمتين باستخدام الدالة Easing.
  • keyframes: مواصفات لتحديد قيم عند نقاط رئيسية معيّنة في أحد الرسوم المتحركة.
  • repeatable: مواصفات مستندة إلى المدة يتم تشغيلها لعدد معيّن من المرات، ويتم تحديدها من خلال RepeatMode.
  • infiniteRepeatable: مواصفات مستندة إلى المدة تعمل بشكل دائم.
  • snap: الانتقال فورًا إلى القيمة النهائية بدون أي مؤثر حركي
اكتب النص البديل هنا
الشكل 16. لم يتم ضبط مواصفات مقابل مجموعة مواصفات الربيع المخصّص

اقرأ المستندات الكاملة لمزيد من المعلومات عن animationSpecs.

مصادر إضافية

للاطلاع على المزيد من الأمثلة على الصور المتحركة الممتعة في Compose، يمكنك الاطّلاع على ما يلي: