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

تتضمّن Compose عناصر قابلة للإنشاء ومعدِّلات مضمّنة للتعامل مع حالات الاستخدام الشائعة للصور المتحركة.

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

توفّر Compose عدة عناصر قابلة للإنشاء تحرّك ظهور المحتوى واختفاءه وتغييرات تنسيقه.

تحريك الظهور والاختفاء

عنصر قابل للإنشاء باللون الأخضر يظهر ويختفي
الشكل 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
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)
    )
}

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

تحريك المحتوى استنادًا إلى الحالة المستهدَفة

يحرّك العنصر القابل للإنشاء 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")
    }
}

تتلاشى تلقائيًا محتويات البداية ثم تتلاشى محتويات الحالة المستهدَفة (يُعرف هذا السلوك باسم التلاشي التدريجي). يمكنك تخصيص سلوك الصورة المتحركة هذا من خلال تحديد ا ContentTransform كائن للمَعلمة transitionSpec. يمكنك إنشاء مثيل من ContentTransform من خلال دمج كائن EnterTransition مع كائن ExitTransition باستخدام الدالة الثنائية 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 المحتوى بين تنسيقَين باستخدام صورة متحركة للتلاشي التدريجي. من خلال تبديل القيمة التي تم تمريرها إلى المَعلمة current، يتم تبديل المحتوى باستخدام صورة متحركة للتلاشي التدريجي.

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

معدِّلات الصور المتحركة المضمّنة

توفّر Compose معدِّلات لتحريك تغييرات معيّنة مباشرةً على العناصر القابلة للإنشاء.

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

تغيير حجم الدالة المركّبة باللون الأخضر بسلاسة
الشكل 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
        }

) {
}

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

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