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

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

إضافة مؤثرات متحركة إلى الخصائص الشائعة القابلة للتجميع

توفّر أداة 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 lambda في العنصر القابل للتجميع 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 لإضافة صور متحركة للتغييرات بين العناصر المختلفة (بطيء)

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

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

استخدِم واجهات برمجة تطبيقات coroutine (Animatable#animateTo() أو animate) أو واجهة برمجة التطبيقات Transition لإنشاء صور متحركة متزامنة. في حال استخدام عدة دالات لبدء في سياق دالة coroutine، يتم تشغيل الرسوم المتحرّكة في الوقت نفسه:

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
    }
}

تحسين أداء الرسوم المتحركة

يمكن أن تتسبب الرسوم المتحرّكة في ميزة "الإنشاء" في حدوث مشاكل في الأداء. ويعود السبب في ذلك إلى طبيعة الرسوم المتحركة: نقل أو تغيير وحدات البكسل على الشاشة بسرعة، إطارًا تلو الآخر لخلق وهم الحركة.

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

لضمان أنّ تطبيقك لا يستهلك الكثير من الموارد أثناء عرض الصور المتحركة، اختَر الإصدار lambda من Modifier كلما أمكن. يؤدي ذلك إلى تخطّي إعادة التركيب وتنفيذ الصورة المتحركة خارج مرحلة التركيب، وإلا استخدِم Modifier.graphicsLayer{ }، لأنّ هذا المُعدِّل يتم تشغيله دائمًا في مرحلة الرسم. لمزيد من المعلومات حول هذا الموضوع، اطّلِع على قسم تأجيل عمليات القراءة في مستندات الأداء.

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

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

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

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

اطّلِع على المستندات الكاملة للحصول على مزيد من المعلومات عن animationSpecs.

مصادر إضافية

لمزيد من الأمثلة على الصور المتحركة الممتعة في ميزة "الإنشاء"، اطّلِع على ما يلي: