أدوات تعديل الصور المتحركة والتركيبات

تأتي أداة Compose مع عناصر قابلة للتجميع وعناصر تعديل مدمجة للتعامل مع حالات الاستخدام الشائعة للرسوم المتحركة.

العناصر المركّبة المتحركة المضمّنة

إضافة مؤثرات متحركة للظهور والاختفاء باستخدام AnimatedVisibility

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

يعرض المكوّن القابل للتجميع AnimatedVisibility مظهر المحتوى واختفائه بشكل متحرك.

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

يظهر المحتوى تلقائيًا من خلال التلاشي والتوسيع، ويختفي من خلال التلاشي والتقلص. يمكن تخصيص الانتقال من خلال تحديد EnterTransition و ExitTransition.

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text(
        "Hello",
        Modifier
            .fillMaxWidth()
            .height(200.dp)
    )
}

كما هو موضّح في المثال أعلاه، يمكنك دمج عناصر EnterTransition أو ExitTransition متعددة باستخدام عامل التشغيل +، ويقبل كلّ منهما مَعلمات اختيارية لتخصيص سلوكه. يمكنك الاطّلاع على المراجع للحصول على مزيد من المعلومات.

أمثلة على EnterTransition وExitTransition

EnterTransition ExitTransition
fadeIn
تأثير التلاشي في الصورة المتحركة
fadeOut
صورة متحركة للاختفاء
slideIn
صورة متحركة للانتقال إلى الشريحة
slideOut
صورة متحركة للخروج
slideInHorizontally
التمرير في رسم متحرك أفقيًا
slideOutHorizontally
رسم متحرك أفقي للخارج
slideInVertically
صورة متحركة للانتقال إلى أعلى الشاشة
slideOutVertically
صورة متحركة للانتقال إلى الخارج بشكل عمودي
scaleIn
الحجم في الرسوم المتحركة
scaleOut
صورة متحركة للتصغير
expandIn
التوسيع في الصورة المتحركة
shrinkOut
صورة متحركة للتقلص
expandHorizontally
تأثير توسيع الصورة أفقيًا
shrinkHorizontally
تقليص الرسوم المتحركة أفقيًا
expandVertically
صورة متحركة للتوسيع بالوضع العمودي
shrinkVertically
تكبير الصورة المتحركة عموديًا

ويوفّر AnimatedVisibility أيضًا صيغة تأخذ MutableTransitionState. يتيح لك ذلك تشغيل صورة متحركة فور إضافة الرمز AnimatedVisibility إلى شجرة التركيب. إنها مفيدة أيضًا لمراقبة حالة الرسوم المتحركة.

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

إضافة حركة للدخول والخروج للأطفال

يمكن للمحتوى ضمن AnimatedVisibility (العناصر المباشرة أو غير المباشرة) استخدام المُعدِّل animateEnterExit لتحديد سلوك مؤثرات متحركة مختلف لكل منها. إنّ التأثير البصري لكلّ من هذه العناصر الفرعية هو عبارة عن تركيبة من الرسوم المتحرّكة المحدّدة في العنصر القابل للتركيب AnimatedVisibility والرسوم المتحرّكة الخاصة بالعنصر الفرعي للدخول والخروج.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

في بعض الحالات، قد تريد عدم السماحAnimatedVisibility بتطبيق أيّ صور متحركة إطلاقًا حتى يتمكّن كل طفل من الحصول على صور متحركة مختلفة منanimateEnterExit. لتحقيق ذلك، حدِّد EnterTransition.None و ExitTransition.None في العنصر القابل للتجميع AnimatedVisibility.

إضافة صورة متحركة مخصّصة

إذا كنت تريد إضافة تأثيرات مخصّصة للحركة إلى جانب تأثيرات الظهور والخروج المضمّنة، يمكنك الوصول إلى مثيل Transition الأساسي من خلال سمة transition داخل دالة lambda للمحتوى في AnimatedVisibility. سيتم تشغيل أيّ حالات لصورة متحركة تمت إضافتها إلى مثيل Transition (الانتقال) في الوقت نفسه مع رسوم التحريك للدخول والخروج في AnimatedVisibility. ينتظر AnimatedVisibility حتى تتم إنهاء جميع الصور المتحركة في Transition قبل إزالة محتواها. بالنسبة إلى الصور المتحركة عند الخروج التي يتم إنشاؤها بشكل مستقل عن Transition (مثل استخدام animate*AsState)، لن يتمكن AnimatedVisibility من حسابها، وبالتالي قد تتم إزالة المحتوى القابل للإنشاء قبل انتهائها.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(background)
    )
}

اطّلِع على updateTransition لمعرفة تفاصيل عن Transition.

إضافة رسوم متحركة استنادًا إلى حالة الاستهداف باستخدام AnimatedContent

يضيف العنصر القابل للتجميع AnimatedContent تأثيرًا متحركًا إلى محتواه عند تغيُّره استنادًا إلى حالة مستهدفة.

Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

يُرجى العِلم أنّه يجب دائمًا استخدام مَعلمة lambda وعرضها في المحتوى. وتستخدم واجهة برمجة التطبيقات هذه القيمة كمفتاح لتحديد المحتوى المعروض حاليًا.

يتم تلقائيًا إخفاء المحتوى الأولي ثم ظهور المحتوى المستهدَف (يُعرف هذا السلوك باسم الاختفاء التدريجي). يمكنك تخصيص سلوك هذه الحركة من خلال تحديد عنصر ContentTransform للمَعلمة transitionSpec. يمكنك إنشاء ContentTransform من خلال الجمع بين EnterTransition وExitTransition باستخدام دالة infix "with". يمكنك تطبيق SizeTransform على ContentTransform من خلال إرفاقه بالدالة using البادئة.

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() togetherWith
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition تحدّد كيفية ظهور المحتوى المستهدَف، و ExitTransition تحدّد كيفية اختفاء المحتوى الأوّلي. بالإضافة إلى جميع الدوال EnterTransition وExitTransition المتاحة في AnimatedVisibility، يوفّر AnimatedContent slideIntoContainer وslideOutOfContainer. هذه بدائل ملائمة لـ slideInHorizontally/Vertically و slideOutHorizontally/Vertically التي تحسب مسافة الشريحة استنادًا إلى أحجام المحتوى الأولي والمحتوى المستهدَف لمحتوى AnimatedContent.

تحدّد السمة SizeTransform الشكل الذي يجب أن يتحرّك الحجم بين المحتوى الأولي والمحتوى المستهدَف. يمكنك الوصول إلى كل من الحجم الأولي والحجم المستهدف عند إنشاء الرسوم المتحركة. تتحكّم السمة SizeTransform أيضًا في ما إذا كان يجب اقتصاص المحتوى إلى حجم المكوِّن أثناء استخدام الصور المتحركة.

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) togetherWith
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }, label = "size transform"
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

إضافة حركة إلى انتقالات دخول الطفل وخروجه

تمامًا مثل AnimatedVisibility، يتوفّر المُعدِّل animateEnterExit داخل دالة lambda للمحتوى في AnimatedContent. استخدِم هذه القيمة لتطبيق EnterAnimation وExitAnimation على كلّ من العناصر الفرعية المباشرة أو غير المباشرة بشكلٍ منفصل.

إضافة صورة متحركة مخصّصة

تمامًا مثل AnimatedVisibility، يتوفر الحقل transition داخل محتوى lambda في AnimatedContent. ويمكنك استخدامها لإنشاء تأثير رسوم متحركة مخصص يتم تشغيله بالتزامن مع تأثير انتقال AnimatedContent. اطّلِع على updateTransition للاطّلاع على التفاصيل.

إضافة تأثيرات متحركة بين تنسيقَين باستخدام Crossfade

يستخدم Crossfade مؤثرات حركية بين تنسيقين باستخدام حركة التلاشي المتقاطع. من خلال تبديل القيمة التي تم تمريرها إلى المَعلمة current، يتم تبديل المحتوى باستخدام تأثير متحرك للانتقال بين الصور.

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

أدوات التعديل المدمجة في الصور المتحركة

تحريك تغييرات الحجم القابلة للإنشاء باستخدام animateContentSize

عنصر أخضر قابل للتجميع يتغيّر حجمه بسلاسة
الشكل 2. أن يكون قابلاً للتجميع مع تأثيرات متحركة سلسة بين حجم صغير وحجم أكبر

يؤدي مفتاح التعديل 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
        }

) {
}

الصور المتحركة لعناصر القائمة

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