راهنمای سریع انیمیشن ها در نوشتن

Compose مکانیزم‌های انیمیشن داخلی زیادی دارد و انتخاب یکی از آنها می‌تواند دشوار باشد. در زیر لیستی از موارد استفاده رایج از انیمیشن آمده است. برای اطلاعات بیشتر در مورد مجموعه کامل گزینه‌های مختلف API موجود، مستندات کامل Compose Animation را مطالعه کنید.

متحرک‌سازی ویژگی‌های ترکیبی رایج

Compose رابط‌های برنامه‌نویسی کاربردی (API) مناسبی ارائه می‌دهد که به شما امکان می‌دهد بسیاری از موارد استفاده رایج از انیمیشن را حل کنید. این بخش نشان می‌دهد که چگونه می‌توانید ویژگی‌های رایج یک composable را متحرک‌سازی کنید.

متحرک سازی ظاهر شدن/ناپدید شدن

سبزِ ترکیب‌پذیر، خود را نشان می‌دهد و پنهان می‌کند
Figure 1. Animating the appearance and disappearance of an item in a Column

Use AnimatedVisibility to hide or show a Composable. Children inside AnimatedVisibility can use Modifier.animateEnterExit() for their own enter or exit transition.

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 به شما امکان می‌دهند نحوه رفتار یک composable را هنگام ظاهر شدن و ناپدید شدن پیکربندی کنید. برای اطلاعات بیشتر ، مستندات کامل را مطالعه کنید.

Another option for animating the visibility of a composable is to animate the alpha over time using 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 در نهایت آیتم را از ترکیب‌بندی حذف می‌کند.

متحرک‌سازی آلفای یک ترکیب‌پذیر
شکل ۲. متحرک‌سازی آلفای یک عنصر ترکیبی

رنگ پس زمینه متحرک

قابل ترکیب با تغییر رنگ پس‌زمینه در طول زمان به عنوان یک انیمیشن، که در آن رنگ‌ها در یکدیگر محو می‌شوند.
شکل ۳. متحرک‌سازی رنگ پس‌زمینه‌ی composable

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

این گزینه نسبت به استفاده از Modifier.background() عملکرد بهتری دارد. Modifier.background() برای تنظیم رنگ یک‌باره قابل قبول است، اما هنگام متحرک‌سازی یک رنگ در طول زمان، می‌تواند باعث تغییر ترکیب‌بندی‌های بیشتر از حد لازم شود.

برای متحرک‌سازی بی‌نهایت رنگ پس‌زمینه، به بخش تکرار یک انیمیشن مراجعه کنید.

متحرک سازی اندازه یک کامپوزبل

سبزِ قابل ترکیب که به طور انیمیشنی تغییر اندازه می‌دهد، روان است.
Figure 4. Composable smoothly animating between a small and a larger size

Compose به شما امکان می‌دهد اندازه composableها را به چند روش مختلف متحرک کنید. برای انیمیشن بین تغییرات اندازه composable 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
        }

) {
}

You can also use AnimatedContent , with a SizeTransform to describe how size changes should take place.

متحرک سازی موقعیت ترکیب پذیر

سبز رنگ با قابلیت انیمیشن روان به سمت پایین و راست
شکل ۵. حرکت ترکیبی با یک جابجایی

برای متحرک‌سازی موقعیت یک composable، از 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
        }
)

اگر می‌خواهید مطمئن شوید که هنگام متحرک‌سازی موقعیت یا اندازه، عناصر ترکیبی (composables) روی یا زیر عناصر ترکیبی دیگر کشیده نمی‌شوند، از Modifier.layout{ } استفاده کنید. این اصلاح‌کننده تغییرات اندازه و موقعیت را به والد منتقل می‌کند که سپس بر سایر فرزندان تأثیر می‌گذارد.

For example, if you are moving a Box within a Column and the other children need to move when the Box moves, include the offset information with Modifier.layout{ } as follows:

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 به آن پاسخ می‌دهد.
شکل ۶. متحرک‌سازی با Modifier.layout{ }

متحرک‌سازی padding یک composable

سبزِ قابل ترکیب با کلیک کوچک و بزرگ می‌شود، و فاصله‌گذاری (padding) متحرک می‌شود
شکل ۷. Composable با انیمیشن padding

برای متحرک‌سازی padding یک composable، از 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
        }
)

متحرک سازی ارتفاع یک ترکیب پذیر

شکل ۸. انیمیشن ارتفاع Composable با کلیک

برای متحرک‌سازی ارتفاع یک عنصر ترکیبی، از 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 composable و تنظیم ویژگی elevation روی مقادیر مختلف در هر حالت است.

متحرک‌سازی مقیاس، ترجمه یا چرخش متن

متن قابل ترکیب با عبارت «سلام» که بین اندازه کوچک و بزرگ متحرک‌سازی می‌شود.
شکل ۹. متحرک‌سازی روان متن بین دو اندازه

هنگام متحرک‌سازی مقیاس، انتقال یا چرخش متن، پارامتر 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)
    )
}

رنگ متن را متحرک کنید

کلمات Hello Compose که رنگشان بین سبز و آبی متحرک است
شکل ۱۰. مثالی که رنگ متن متحرک را نشان می‌دهد

To animate text color, use the color lambda on the BasicText composable:

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

جابجایی بین انواع مختلف محتوا

صفحه سبز می‌گوید در حال بارگذاری، صفحه آبی می‌گوید بارگذاری شده و صفحه سفید می‌گوید خطا، ترکیب‌های مختلف با یک انیمیشن ساده بین آنها تکرار می‌شوند.
شکل ۱۱. استفاده از AnimatedContent برای متحرک‌سازی تغییرات بین composableهای مختلف (با سرعت کم)

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 را مطالعه کنید.

متحرک‌سازی هنگام پیمایش به مقاصد مختلف

دو ترکیب‌پذیر، یکی سبز که نشان‌دهنده‌ی فرود است و دیگری آبی که نشان‌دهنده‌ی جزئیات است، که با لغزاندن ترکیب‌پذیر جزئیات روی ترکیب‌پذیر فرود، متحرک‌سازی می‌شوند.
Figure 12. Animating between composables using navigation-compose

برای متحرک‌سازی انتقال بین کامپوننت‌ها هنگام استفاده از navigation-compose artifact، 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(
            // ...
        )
    }
}

انواع مختلفی از انتقال‌های ورود و خروج وجود دارد که جلوه‌های متفاوتی را بر محتوای ورودی و خروجی اعمال می‌کنند، برای اطلاعات بیشتر به مستندات مراجعه کنید.

تکرار یک انیمیشن

یک پس‌زمینه سبز که با انیمیشن بین این دو رنگ، به طور نامحدود به یک پس‌زمینه آبی تبدیل می‌شود.
Figure 13. Background color animating between two values, infinitely

rememberInfiniteTransition به همراه یک infiniteRepeatable animationSpec برای تکرار مداوم انیمیشن خود استفاده کنید. RepeatModes تغییر دهید تا نحوه رفت و برگشت آن را مشخص کنید.

repeatable برای تکرار به تعداد دفعات مشخص استفاده کنید.

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
}

شروع یک انیمیشن با اجرای یک composable

LaunchedEffect زمانی اجرا می‌شود که یک composable وارد کامپوزیشن شود. این تابع با اجرای یک composable، یک انیمیشن را شروع می‌کند، می‌توانید از این برای تغییر حالت انیمیشن استفاده کنید. استفاده از Animatable با متد animateTo برای شروع انیمیشن در هنگام اجرا:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

ایجاد انیمیشن‌های متوالی

چهار دایره با فلش‌های سبز که بین هر کدام متحرک هستند، یکی پس از دیگری متحرک می‌شوند.
شکل ۱۴. نموداری که نشان می‌دهد یک انیمیشن متوالی چگونه یک به یک پیشرفت می‌کند.

از APIهای کوروتین Animatable برای اجرای انیمیشن‌های متوالی یا همزمان استفاده کنید. فراخوانی animateTo روی Animatable یکی پس از دیگری باعث می‌شود هر انیمیشن قبل از ادامه منتظر پایان انیمیشن‌های قبلی بماند. دلیل این امر این است که این یک تابع suspend است.

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    alphaAnimation.animateTo(1f)
    yAnimation.animateTo(100f)
    yAnimation.animateTo(500f, animationSpec = tween(100))
}

ایجاد انیمیشن‌های همزمان

سه دایره با فلش‌های سبز که به هر کدام انیمیشن می‌دهند، و همه با هم همزمان انیمیشن ایجاد می‌کنند.
شکل ۱۵. نموداری که نشان می‌دهد انیمیشن‌های همزمان چگونه به طور همزمان پیشرفت می‌کنند.

از APIهای کوروتین ( Animatable#animateTo() یا animate ) یا API Transition برای دستیابی به انیمیشن‌های همزمان استفاده کنید. اگر از چندین تابع راه‌اندازی در یک زمینه کوروتین استفاده کنید، انیمیشن‌ها را همزمان راه‌اندازی می‌کند:

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    launch {
        alphaAnimation.animateTo(1f)
    }
    launch {
        yAnimation.animateTo(100f)
    }
}

شما می‌توانید از API updateTransition برای استفاده از یک state یکسان جهت اجرای انیمیشن‌های مختلف برای ویژگی‌های مختلف به طور همزمان استفاده کنید. مثال زیر دو ویژگی را که توسط یک state به نام change کنترل می‌شوند، متحرک‌سازی می‌کند: 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 را در نظر بگیرید: ترکیب‌بندی، طرح‌بندی و رسم. اگر انیمیشن شما مرحله طرح‌بندی را تغییر دهد، لازم است همه Composableهای تحت تأثیر قرار گرفته، رله و رسم مجدد شوند. اگر انیمیشن شما در مرحله رسم اجرا شود، به طور پیش‌فرض عملکرد بهتری نسبت به زمانی دارد که انیمیشن را در مرحله طرح‌بندی اجرا کنید، زیرا در کل کار کمتری برای انجام دادن خواهد داشت.

برای اطمینان از اینکه برنامه شما هنگام انیمیشن‌سازی تا حد امکان کمترین کار را انجام می‌دهد، در صورت امکان نسخه لامبدا از یک Modifier را انتخاب کنید. این کار از ترکیب مجدد صرف‌نظر می‌کند و انیمیشن را خارج از مرحله ترکیب اجرا می‌کند، در غیر این صورت از Modifier.graphicsLayer{ } استفاده کنید، زیرا این اصلاح‌کننده همیشه در مرحله ترسیم اجرا می‌شود. برای اطلاعات بیشتر در این مورد، به بخش تعویق خواندن در مستندات عملکرد مراجعه کنید.

تغییر زمان انیمیشن

Compose by default uses spring animations for most animations. Springs, or physics-based animations, feel more natural. They are also interruptible as they take into account the object's current velocity, instead of a fixed time. If you want to override the default, all the animation APIs demonstrated previously have the ability to set an animationSpec to customize how an animation runs, whether you'd like it to execute over a certain duration or be more bouncy.

در زیر خلاصه‌ای از گزینه‌های مختلف animationSpec آمده است:

  • spring : انیمیشن مبتنی بر فیزیک، پیش‌فرض برای همه انیمیشن‌ها. می‌توانید سختی یا نسبت میرایی را تغییر دهید تا به ظاهر و حس انیمیشن متفاوتی دست یابید.
  • tween (مخفف between ): انیمیشن مبتنی بر مدت زمان، انیمیشن‌ها را بین دو مقدار با استفاده از یک تابع Easing انجام می‌دهد.
  • keyframes : Spec for specifying values at certain key points in an animation.
  • repeatable : مشخصات مبتنی بر مدت زمان که تعداد دفعات مشخصی اجرا می‌شود و توسط RepeatMode مشخص می‌شود.
  • infiniteRepeatable : مشخصات مبتنی بر مدت زمان که برای همیشه اجرا می‌شود.
  • snap : فوراً و بدون هیچ انیمیشنی به مقدار پایانی می‌چسبد.
دو انیمیشن که عدم وجود مجموعه مشخصات را در مقابل یک مجموعه مشخصات سفارشی Spring نشان می‌دهند.
شکل ۱۶. بدون مجموعه مشخصات در مقابل مجموعه مشخصات سفارشی اسپرینگ

برای اطلاعات بیشتر در مورد animationSpecs، مستندات کامل را مطالعه کنید.

منابع اضافی

برای مثال‌های بیشتر از انیمیشن‌های سرگرم‌کننده در Compose، به موارد زیر نگاهی بیندازید: