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

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

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

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

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

سبزِ ترکیب‌پذیر، خود را نشان می‌دهد و پنهان می‌کند
شکل ۱. متحرک‌سازی ظاهر و ناپدید شدن یک آیتم در یک ستون

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

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

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

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

سبزِ قابل ترکیب که به طور انیمیشنی تغییر اندازه می‌دهد، روان است.
شکل ۴. انیمیشن روان و قابل ترکیب بین اندازه کوچک و بزرگتر

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
        }

) {
}

همچنین می‌توانید از AnimatedContent به همراه SizeTransform برای توصیف نحوه‌ی اعمال تغییرات اندازه استفاده کنید.

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

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

برای متحرک‌سازی موقعیت یک 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{ } استفاده کنید. این اصلاح‌کننده تغییرات اندازه و موقعیت را به والد منتقل می‌کند که سپس بر سایر فرزندان تأثیر می‌گذارد.

برای مثال، اگر می‌خواهید یک 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 به آن پاسخ می‌دهد.
شکل ۶. متحرک‌سازی با 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 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)
    )
}

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

کلمات
شکل ۱۰. مثالی که رنگ متن متحرک را نشان می‌دهد

برای متحرک‌سازی رنگ متن، از لامبدا color در کامپوننت 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
    },
    // ...
)

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

جمله روی پرده سبز
شکل ۱۱. استفاده از 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 مطالعه کنید.

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

دو ترکیب‌پذیر، یکی سبز که نشان‌دهنده‌ی فرود است و دیگری آبی که نشان‌دهنده‌ی جزئیات است، که با لغزاندن ترکیب‌پذیر جزئیات روی ترکیب‌پذیر فرود، متحرک‌سازی می‌شوند.
شکل ۱۲. متحرک‌سازی بین composableها با استفاده از 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(
            // ...
        )
    }
}

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

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

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

از 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 به طور پیش‌فرض از انیمیشن‌های فنری برای اکثر انیمیشن‌ها استفاده می‌کند. فنرها یا انیمیشن‌های مبتنی بر فیزیک، طبیعی‌تر به نظر می‌رسند. آن‌ها همچنین قابل وقفه هستند زیرا به جای یک زمان ثابت، سرعت فعلی شیء را در نظر می‌گیرند. اگر می‌خواهید پیش‌فرض را نادیده بگیرید، تمام APIهای انیمیشن نشان داده شده در بالا قابلیت تنظیم animationSpec را برای سفارشی‌سازی نحوه اجرای یک انیمیشن دارند، چه بخواهید در مدت زمان مشخصی اجرا شود یا بیشتر حالت فنری داشته باشد.

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

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

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

منابع اضافی

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