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

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

ویژگی های متداول ترکیب پذیر را متحرک کنید

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

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

رنگ سبز خود را نشان می دهد و پنهان می کند
شکل 1. متحرک سازی ظاهر و ناپدید شدن یک آیتم در یک ستون

از 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 در نهایت آیتم را از ترکیب حذف می کند.

متحرک سازی آلفای یک آهنگساز
شکل 2. متحرک سازی آلفای یک ترکیب ساز

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

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

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

متحرک کردن به اندازه یک ترکیب

متحرک سازی رنگ سبز، اندازه آن به آرامی تغییر می کند.
شکل 4. متحرک سازی هموار بین اندازه کوچک و بزرگتر

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

سبز قابل ترکیب به آرامی متحرک پایین و سمت راست
شکل 5. متحرک با افست قابل ترکیب

برای متحرک کردن موقعیت یک 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
        }
)

اگر می‌خواهید هنگام متحرک کردن موقعیت یا اندازه، مطمئن شوید که Composable‌ها روی یا زیر دیگر composable‌ها کشیده نمی‌شوند، از 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)
    )
}

2 جعبه با جعبه دوم که موقعیت X,Y خود را متحرک می کند، جعبه سوم با حرکت دادن خود به مقدار Y نیز پاسخ می دهد.
شکل 6. متحرک سازی با Modifier.layout{ }

متحرک کردن بالشتک از یک composable

ترکیب سبز با کلیک کوچکتر و بزرگتر می شود و بالشتک متحرک می شود
شکل 7. قابل ترکیب با متحرک سازی آن

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

متحرک کردن ارتفاع یک ترکیب پذیر

شکل 8. متحرک سازی ارتفاع Composable با کلیک

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

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

گفتار قابل ساخت متن
شکل 9. متحرک سازی متن به آرامی بین دو اندازه

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

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

کلمات
شکل 10. مثالی که رنگ متن متحرک را نشان می دهد

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

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

صفحه سبز می گوید
شکل 11. استفاده از AnimatedContent برای متحرک سازی تغییرات بین اجزای سازنده مختلف (کاهش سرعت)

از 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 بخوانید.

در حین حرکت به مقاصد مختلف متحرک شوید

دو قابل ترکیب، یکی سبز که می‌گوید Landing و دیگری آبی که می‌گوید Detail، با لغزش جزئیات قابل ساخت بر روی فرود composable متحرک می‌شود.
شکل 12. متحرک سازی بین اجزای سازنده با استفاده از navigation-compose

برای متحرک سازی انتقال بین اجزای سازنده هنگام استفاده از مصنوع ناوبری-نوشتن ، enterTransition و exitTransition در یک composable مشخص کنید. همچنین می‌توانید انیمیشن پیش‌فرض را برای استفاده برای همه مقاصد در سطح بالای 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(
            // ...
        )
    }
}

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

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

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

از rememberInfiniteTransition با animationSpec infiniteRepeatable برای تکرار مداوم انیمیشن خود استفاده کنید. RepeatModes را تغییر دهید تا مشخص کنید چگونه باید به جلو و عقب برود.

از finiteRepeatable برای تکرار چندین بار استفاده کنید.

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 وارد ترکیب شود. با راه‌اندازی یک کامپوزیشن، یک انیمیشن را شروع می‌کند، می‌توانید از آن برای تغییر حالت انیمیشن استفاده کنید. استفاده از Animatable با متد animateTo برای شروع انیمیشن هنگام راه اندازی:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Compose به طور پیش فرض از انیمیشن های فنری برای اکثر انیمیشن ها استفاده می کند. فنرها یا انیمیشن های مبتنی بر فیزیک، طبیعی تر هستند. آنها همچنین قابل وقفه هستند زیرا به جای زمان ثابت، سرعت فعلی جسم را در نظر می گیرند. اگر می‌خواهید پیش‌فرض را لغو کنید، همه APIهای انیمیشنی که در بالا نشان داده شده‌اند، این توانایی را دارند که animationSpec را برای سفارشی کردن نحوه اجرای یک انیمیشن تنظیم کنند، چه بخواهید در مدت زمان معینی اجرا شود یا بیشتر پرشور باشد.

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

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

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

منابع اضافی

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