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

تتضمّن 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
slide in horizontally animation
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 باستخدام الدالة 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
        }

) {
}

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

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