انیمیشن های مبتنی بر ارزش

با animate*AsState یک مقدار را متحرک کنید

توابع animate*AsState ساده ترین APIهای انیمیشن در Compose برای متحرک کردن یک مقدار واحد هستند. شما فقط مقدار هدف (یا مقدار پایان) را ارائه می دهید و API انیمیشن را از مقدار فعلی به مقدار مشخص شده شروع می کند.

در زیر نمونه ای از متحرک سازی آلفا با استفاده از این API آورده شده است. به سادگی با قرار دادن مقدار هدف در animateFloatAsState ، مقدار آلفا اکنون یک مقدار انیمیشن بین مقادیر ارائه شده است (در این مورد 1f یا 0.5f ).

var enabled by remember { mutableStateOf(true) }

val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)

توجه داشته باشید که نیازی به ایجاد یک نمونه از کلاس انیمیشن یا مدیریت وقفه ندارید. در زیر هود، یک شی انیمیشن (یعنی یک نمونه Animatable ) ایجاد می شود و در سایت فراخوانی به خاطر سپرده می شود، با اولین مقدار هدف به عنوان مقدار اولیه آن. از آنجا به بعد، هر زمان که مقدار هدف متفاوتی را به این قابل ترکیب بدهید، یک انیمیشن به طور خودکار به سمت آن مقدار شروع می شود. اگر از قبل یک انیمیشن در حال پرواز وجود داشته باشد، انیمیشن از مقدار (و سرعت) فعلی شروع می شود و به سمت مقدار هدف حرکت می کند. در طول انیمیشن، این قابل ترکیب دوباره ترکیب می شود و هر فریم یک مقدار انیمیشن به روز شده را برمی گرداند.

خارج از جعبه، Compose توابع animate*AsState را برای Float ، Color ، Dp ، Size ، Offset ، Rect ، Int ، IntOffset و IntSize فراهم می کند. شما به راحتی می توانید با ارائه یک TwoWayConverter به animateValueAsState که نوع عمومی را می گیرد، پشتیبانی از انواع داده های دیگر اضافه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

چندین ویژگی را به طور همزمان با یک انتقال متحرک کنید

Transition یک یا چند انیمیشن را به عنوان فرزند خود مدیریت می کند و آنها را به طور همزمان بین چندین حالت اجرا می کند.

حالت ها می توانند از هر نوع داده ای باشند. در بسیاری از موارد، می توانید از یک نوع enum سفارشی برای اطمینان از ایمنی نوع استفاده کنید، مانند این مثال:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition یک نمونه از Transition را ایجاد و به خاطر می آورد و وضعیت آن را به روز می کند.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

سپس می توانید از یکی از توابع پسوند animate* برای تعریف یک انیمیشن فرزند در این انتقال استفاده کنید. مقادیر هدف را برای هر یک از حالت ها مشخص کنید. این توابع animate* یک مقدار انیمیشن را برمی‌گردانند که در طول انیمیشن زمانی که وضعیت انتقال با updateTransition به‌روزرسانی می‌شود، در هر فریم به‌روزرسانی می‌شود.

val rect by transition.animateRect(label = "rectangle") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "border width") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

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

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

هنگامی که یک انتقال به حالت هدف رسید، Transition.currentState مانند Transition.targetState خواهد بود. این می تواند به عنوان یک سیگنال برای اینکه آیا انتقال به پایان رسیده است استفاده می شود.

ما گاهی می خواهیم یک حالت اولیه متفاوت از حالت هدف اول داشته باشیم. برای رسیدن به این هدف می توانیم updateTransition با MutableTransitionState استفاده کنیم. به عنوان مثال، به ما این امکان را می دهد که به محض ورود کد به ترکیب، انیمیشن را شروع کنیم.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

برای انتقال پیچیده‌تر که شامل چندین تابع قابل ترکیب است، می‌توانید از createChildTransition برای ایجاد یک انتقال فرزند استفاده کنید. این تکنیک برای جداسازی نگرانی‌ها در میان اجزای فرعی متعدد در یک ترکیب پیچیده مفید است. انتقال والد از تمام مقادیر انیمیشن در انتقال فرزند آگاه خواهد بود.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

از انتقال با AnimatedVisibility و AnimatedContent استفاده کنید

AnimatedVisibility و AnimatedContent به عنوان توابع پسوند Transition در دسترس هستند. targetState برای Transition.AnimatedVisibility و Transition.AnimatedContent از Transition مشتق شده است و هنگامی که targetState Transition تغییر کرده است، در صورت نیاز، انتقالات ورود/خروج را آغاز می کند. این توابع افزودنی به همه انیمیشن‌های enter/exit/sizeTransform که در غیر این صورت داخلی AnimatedVisibility / AnimatedContent هستند اجازه می‌دهند تا در Transition قرار بگیرند. با این توابع افزودنی، تغییر حالت AnimatedVisibility / AnimatedContent را می توان از خارج مشاهده کرد. به جای یک پارامتر visible بولی، این نسخه از AnimatedVisibility یک لامبدا می گیرد که حالت هدف انتقال والد را به یک بولی تبدیل می کند.

برای جزئیات بیشتر به AnimatedVisibility و AnimatedContent مراجعه کنید.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

یک انتقال را کپسوله کنید و آن را قابل استفاده مجدد کنید

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

شما می توانید این کار را با ایجاد یک کلاس که تمام مقادیر انیمیشن را در خود نگه می دارد و یک تابع 'update' که نمونه ای از آن کلاس را برمی گرداند، انجام دهید. پیاده سازی انتقال را می توان در تابع جداگانه جدید استخراج کرد. این الگو زمانی مفید است که نیاز به متمرکز کردن منطق انیمیشن یا ساخت انیمیشن های پیچیده قابل استفاده مجدد باشد.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

با rememberInfiniteTransition یک انیمیشن بی نهایت تکراری ایجاد کنید

InfiniteTransition دارای یک یا چند انیمیشن کودک مانند Transition است، اما انیمیشن ها به محض ورود به ترکیب شروع به اجرا می کنند و متوقف نمی شوند مگر اینکه حذف شوند. می توانید یک نمونه از InfiniteTransition با rememberInfiniteTransition ایجاد کنید. انیمیشن های کودک را می توان با animateColor ، animatedFloat ، یا animatedValue اضافه کرد. همچنین برای تعیین مشخصات انیمیشن باید یک infiniteRepeatable را مشخص کنید.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

API های انیمیشن سطح پایین

تمام API های انیمیشن سطح بالا که در بخش قبل ذکر شد، بر روی پایه API های انیمیشن سطح پایین ساخته شده اند.

توابع animate*AsState ساده ترین API ها هستند که تغییر مقدار آنی را به عنوان یک مقدار انیمیشن ارائه می کنند. توسط Animatable پشتیبانی می شود، که یک API مبتنی بر کوروتین برای متحرک سازی یک مقدار واحد است. updateTransition یک شی انتقال ایجاد می کند که می تواند چندین مقدار متحرک را مدیریت کند و آنها را بر اساس تغییر حالت اجرا کند. rememberInfiniteTransition مشابه است، اما یک انتقال بی نهایت ایجاد می کند که می تواند چندین انیمیشن را مدیریت کند که به طور نامحدود در حال اجرا هستند. همه این APIها قابل ترکیب هستند به جز Animatable ، به این معنی که این انیمیشن ها را می توان خارج از ترکیب ایجاد کرد.

همه این APIها بر اساس API اساسی‌تر Animation هستند. اگرچه بیشتر برنامه‌ها مستقیماً با Animation تعامل ندارند، برخی از قابلیت‌های سفارشی‌سازی Animation از طریق APIهای سطح بالاتر در دسترس هستند. برای اطلاعات بیشتر در مورد AnimationVector و AnimationSpec به سفارشی کردن انیمیشن ها مراجعه کنید.

نموداری که رابطه بین API های مختلف انیمیشن سطح پایین را نشان می دهد

Animatable : انیمیشن تک ارزشی مبتنی بر Coroutine

Animatable یک دارنده مقدار است که می تواند مقدار را با تغییر آن از طریق animateTo متحرک کند. این API پشتیبان‌گیری از اجرای animate*AsState است. این تداوم مداوم و انحصار متقابل را تضمین می کند، به این معنی که تغییر ارزش همیشه مداوم است و هر انیمیشن در حال انجام لغو خواهد شد.

بسیاری از ویژگی های Animatable ، از جمله animateTo ، به عنوان توابع تعلیق ارائه شده است. این بدان معنی است که آنها باید در یک محدوده کوروتین مناسب پیچیده شوند. برای مثال، می‌توانید از ترکیب‌کننده LaunchedEffect برای ایجاد یک محدوده فقط برای مدت زمان مقدار کلید مشخص شده استفاده کنید.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

در مثال بالا، یک نمونه از Animatable با مقدار اولیه Color.Gray را ایجاد کرده و به خاطر می آوریم. بسته به مقدار پرچم بولی ok ، رنگ به رنگ Color.Green یا Color.Red متحرک می شود. هر تغییر بعدی در مقدار بولی باعث شروع انیمیشن به رنگ دیگر می شود. اگر هنگام تغییر مقدار، انیمیشنی در حال انجام باشد، انیمیشن لغو می‌شود و انیمیشن جدید از مقدار عکس فوری فعلی با سرعت فعلی شروع می‌شود.

این پیاده‌سازی انیمیشنی است که از animate*AsState API ذکر شده در بخش قبل پشتیبانی می‌کند. در مقایسه با animate*AsState ، استفاده از Animatable مستقیماً از چندین جنبه به ما کنترل دقیق تری می دهد. اول، Animatable می تواند یک مقدار اولیه متفاوت از اولین مقدار هدف خود داشته باشد. به عنوان مثال، مثال کد بالا در ابتدا یک کادر خاکستری را نشان می دهد که بلافاصله شروع به متحرک شدن به رنگ سبز یا قرمز می کند. دوم، Animatable عملیات بیشتری را روی مقدار محتوا، یعنی snapTo و animateDecay ارائه می‌کند. snapTo مقدار فعلی را بلافاصله به مقدار هدف تنظیم می کند. این زمانی مفید است که خود انیمیشن تنها منبع حقیقت نیست و باید با سایر حالت‌ها، مانند رویدادهای لمسی، همگام شود. animateDecay انیمیشنی را شروع می کند که از سرعت داده شده کاهش می یابد. این برای اجرای رفتار پرت کردن مفید است. برای اطلاعات بیشتر به ژست و انیمیشن مراجعه کنید.

خارج از جعبه، Animatable از Float و Color پشتیبانی می کند، اما هر نوع داده ای را می توان با ارائه یک TwoWayConverter استفاده کرد. برای اطلاعات بیشتر به AnimationVector مراجعه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

Animation : انیمیشن با کنترل دستی

Animation پایین‌ترین API انیمیشن موجود است. بسیاری از انیمیشن هایی که تاکنون دیده ایم بر روی انیمیشن ساخته شده اند. دو نوع Animation وجود دارد: TargetBasedAnimation و DecayAnimation .

Animation فقط باید برای کنترل دستی زمان انیمیشن استفاده شود. Animation بدون حالت است و هیچ مفهومی از چرخه حیات ندارد. این به عنوان یک موتور محاسبه انیمیشن عمل می کند که API های سطح بالاتر از آن استفاده می کنند.

TargetBasedAnimation

سایر API ها بیشتر موارد استفاده را پوشش می دهند، اما استفاده از TargetBasedAnimation به طور مستقیم به شما امکان می دهد زمان پخش انیمیشن را خودتان کنترل کنید. در مثال زیر، زمان پخش TargetAnimation به صورت دستی بر اساس زمان فریم ارائه شده توسط withFrameNanos کنترل می شود.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

برخلاف TargetBasedAnimation ، DecayAnimation نیازی به ارائه targetValue ندارد. درعوض، targetValue خود را بر اساس شرایط شروع محاسبه می‌کند، که توسط initialVelocity و initialValue و DecayAnimationSpec ارائه شده است.

انیمیشن‌های Decay اغلب پس از حرکت حرکتی برای کاهش سرعت عناصر تا توقف استفاده می‌شوند. سرعت انیمیشن از مقدار تعیین شده توسط initialVelocityVector شروع می شود و با گذشت زمان کند می شود.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% endverbatim %}،

با animate*AsState یک مقدار را متحرک کنید

توابع animate*AsState ساده ترین APIهای انیمیشن در Compose برای متحرک کردن یک مقدار واحد هستند. شما فقط مقدار هدف (یا مقدار پایان) را ارائه می دهید و API انیمیشن را از مقدار فعلی به مقدار مشخص شده شروع می کند.

در زیر نمونه ای از متحرک سازی آلفا با استفاده از این API آورده شده است. به سادگی با قرار دادن مقدار هدف در animateFloatAsState ، مقدار آلفا اکنون یک مقدار انیمیشن بین مقادیر ارائه شده است (در این مورد 1f یا 0.5f ).

var enabled by remember { mutableStateOf(true) }

val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)

توجه داشته باشید که نیازی به ایجاد یک نمونه از کلاس انیمیشن یا مدیریت وقفه ندارید. در زیر هود، یک شی انیمیشن (یعنی یک نمونه Animatable ) ایجاد می شود و در سایت فراخوانی به خاطر سپرده می شود، با اولین مقدار هدف به عنوان مقدار اولیه آن. از آنجا به بعد، هر زمان که مقدار هدف متفاوتی را به این قابل ترکیب بدهید، یک انیمیشن به طور خودکار به سمت آن مقدار شروع می شود. اگر از قبل یک انیمیشن در حال پرواز وجود داشته باشد، انیمیشن از مقدار (و سرعت) فعلی شروع می شود و به سمت مقدار هدف حرکت می کند. در طول انیمیشن، این قابل ترکیب دوباره ترکیب می شود و هر فریم یک مقدار انیمیشن به روز شده را برمی گرداند.

خارج از جعبه، Compose توابع animate*AsState را برای Float ، Color ، Dp ، Size ، Offset ، Rect ، Int ، IntOffset و IntSize فراهم می کند. شما به راحتی می توانید با ارائه یک TwoWayConverter به animateValueAsState که نوع عمومی را می گیرد، پشتیبانی از انواع داده های دیگر اضافه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

چندین ویژگی را به طور همزمان با یک انتقال متحرک کنید

Transition یک یا چند انیمیشن را به عنوان فرزند خود مدیریت می کند و آنها را به طور همزمان بین چندین حالت اجرا می کند.

حالت ها می توانند از هر نوع داده ای باشند. در بسیاری از موارد، می توانید از یک نوع enum سفارشی برای اطمینان از ایمنی نوع استفاده کنید، مانند این مثال:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition یک نمونه از Transition را ایجاد و به خاطر می آورد و وضعیت آن را به روز می کند.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

سپس می توانید از یکی از توابع پسوند animate* برای تعریف یک انیمیشن فرزند در این انتقال استفاده کنید. مقادیر هدف را برای هر یک از حالت ها مشخص کنید. این توابع animate* یک مقدار انیمیشن را برمی‌گردانند که در طول انیمیشن زمانی که وضعیت انتقال با updateTransition به‌روزرسانی می‌شود، در هر فریم به‌روزرسانی می‌شود.

val rect by transition.animateRect(label = "rectangle") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "border width") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

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

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

هنگامی که یک انتقال به حالت هدف رسید، Transition.currentState مانند Transition.targetState خواهد بود. این می تواند به عنوان یک سیگنال برای اینکه آیا انتقال به پایان رسیده است استفاده می شود.

ما گاهی می خواهیم یک حالت اولیه متفاوت از حالت هدف اول داشته باشیم. برای رسیدن به این هدف می توانیم updateTransition با MutableTransitionState استفاده کنیم. به عنوان مثال، به ما این امکان را می دهد که به محض ورود کد به ترکیب، انیمیشن را شروع کنیم.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

برای انتقال پیچیده‌تر که شامل چندین تابع قابل ترکیب است، می‌توانید از createChildTransition برای ایجاد یک انتقال فرزند استفاده کنید. این تکنیک برای جداسازی نگرانی‌ها در میان اجزای فرعی متعدد در یک ترکیب پیچیده مفید است. انتقال والد از تمام مقادیر انیمیشن در انتقال فرزند آگاه خواهد بود.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

از انتقال با AnimatedVisibility و AnimatedContent استفاده کنید

AnimatedVisibility و AnimatedContent به عنوان توابع پسوند Transition در دسترس هستند. targetState برای Transition.AnimatedVisibility و Transition.AnimatedContent از Transition مشتق شده است و هنگامی که targetState Transition تغییر کرده است، در صورت نیاز، انتقالات ورود/خروج را آغاز می کند. این توابع افزودنی به همه انیمیشن‌های enter/exit/sizeTransform که در غیر این صورت داخلی AnimatedVisibility / AnimatedContent هستند اجازه می‌دهند تا در Transition قرار بگیرند. با این توابع افزودنی، تغییر حالت AnimatedVisibility / AnimatedContent را می توان از خارج مشاهده کرد. به جای یک پارامتر visible بولی، این نسخه از AnimatedVisibility یک لامبدا می گیرد که حالت هدف انتقال والد را به یک بولی تبدیل می کند.

برای جزئیات بیشتر به AnimatedVisibility و AnimatedContent مراجعه کنید.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

یک انتقال را کپسوله کنید و آن را قابل استفاده مجدد کنید

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

شما می توانید این کار را با ایجاد یک کلاس که تمام مقادیر انیمیشن را در خود نگه می دارد و یک تابع 'update' که نمونه ای از آن کلاس را برمی گرداند، انجام دهید. پیاده سازی انتقال را می توان در تابع جداگانه جدید استخراج کرد. این الگو زمانی مفید است که نیاز به متمرکز کردن منطق انیمیشن یا ساخت انیمیشن های پیچیده قابل استفاده مجدد باشد.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

با rememberInfiniteTransition یک انیمیشن بی نهایت تکراری ایجاد کنید

InfiniteTransition دارای یک یا چند انیمیشن کودک مانند Transition است، اما انیمیشن ها به محض ورود به ترکیب شروع به اجرا می کنند و متوقف نمی شوند مگر اینکه حذف شوند. می توانید یک نمونه از InfiniteTransition با rememberInfiniteTransition ایجاد کنید. انیمیشن های کودک را می توان با animateColor ، animatedFloat ، یا animatedValue اضافه کرد. همچنین برای تعیین مشخصات انیمیشن باید یک infiniteRepeatable را مشخص کنید.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

API های انیمیشن سطح پایین

تمام API های انیمیشن سطح بالا که در بخش قبل ذکر شد، بر روی پایه API های انیمیشن سطح پایین ساخته شده اند.

توابع animate*AsState ساده ترین API ها هستند که تغییر مقدار آنی را به عنوان یک مقدار انیمیشن ارائه می کنند. توسط Animatable پشتیبانی می شود، که یک API مبتنی بر کوروتین برای متحرک سازی یک مقدار واحد است. updateTransition یک شی انتقال ایجاد می کند که می تواند چندین مقدار متحرک را مدیریت کند و آنها را بر اساس تغییر حالت اجرا کند. rememberInfiniteTransition مشابه است، اما یک انتقال بی نهایت ایجاد می کند که می تواند چندین انیمیشن را مدیریت کند که به طور نامحدود در حال اجرا هستند. همه این APIها قابل ترکیب هستند به جز Animatable ، به این معنی که این انیمیشن ها را می توان خارج از ترکیب ایجاد کرد.

همه این APIها بر اساس API اساسی‌تر Animation هستند. اگرچه بیشتر برنامه‌ها مستقیماً با Animation تعامل ندارند، برخی از قابلیت‌های سفارشی‌سازی Animation از طریق APIهای سطح بالاتر در دسترس هستند. برای اطلاعات بیشتر در مورد AnimationVector و AnimationSpec به سفارشی کردن انیمیشن ها مراجعه کنید.

نموداری که رابطه بین API های مختلف انیمیشن سطح پایین را نشان می دهد

Animatable : انیمیشن تک ارزشی مبتنی بر Coroutine

Animatable یک دارنده مقدار است که می تواند مقدار را با تغییر آن از طریق animateTo متحرک کند. این API پشتیبان‌گیری از اجرای animate*AsState است. این تداوم مداوم و انحصار متقابل را تضمین می کند، به این معنی که تغییر ارزش همیشه مداوم است و هر انیمیشن در حال انجام لغو خواهد شد.

بسیاری از ویژگی های Animatable ، از جمله animateTo ، به عنوان توابع تعلیق ارائه شده است. این بدان معنی است که آنها باید در یک محدوده کوروتین مناسب پیچیده شوند. برای مثال، می‌توانید از ترکیب‌کننده LaunchedEffect برای ایجاد یک محدوده فقط برای مدت زمان مقدار کلید مشخص شده استفاده کنید.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

در مثال بالا، یک نمونه از Animatable با مقدار اولیه Color.Gray را ایجاد کرده و به خاطر می آوریم. بسته به مقدار پرچم بولی ok ، رنگ به رنگ Color.Green یا Color.Red متحرک می شود. هر تغییر بعدی در مقدار بولی باعث شروع انیمیشن به رنگ دیگر می شود. اگر هنگام تغییر مقدار، انیمیشنی در حال انجام باشد، انیمیشن لغو می‌شود و انیمیشن جدید از مقدار عکس فوری فعلی با سرعت فعلی شروع می‌شود.

این پیاده‌سازی انیمیشنی است که از animate*AsState API ذکر شده در بخش قبل پشتیبانی می‌کند. در مقایسه با animate*AsState ، استفاده از Animatable مستقیماً از چندین جنبه به ما کنترل دقیق تری می دهد. اول، Animatable می تواند یک مقدار اولیه متفاوت از اولین مقدار هدف خود داشته باشد. به عنوان مثال، مثال کد بالا در ابتدا یک کادر خاکستری را نشان می دهد که بلافاصله شروع به متحرک شدن به رنگ سبز یا قرمز می کند. دوم، Animatable عملیات بیشتری را روی مقدار محتوا، یعنی snapTo و animateDecay ارائه می‌کند. snapTo مقدار فعلی را بلافاصله به مقدار هدف تنظیم می کند. این زمانی مفید است که خود انیمیشن تنها منبع حقیقت نیست و باید با سایر حالت‌ها، مانند رویدادهای لمسی، همگام شود. animateDecay انیمیشنی را شروع می کند که از سرعت داده شده کاهش می یابد. این برای اجرای رفتار پرت کردن مفید است. برای اطلاعات بیشتر به ژست و انیمیشن مراجعه کنید.

خارج از جعبه، Animatable از Float و Color پشتیبانی می کند، اما هر نوع داده ای را می توان با ارائه یک TwoWayConverter استفاده کرد. برای اطلاعات بیشتر به AnimationVector مراجعه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

Animation : انیمیشن با کنترل دستی

Animation پایین‌ترین API انیمیشن موجود است. بسیاری از انیمیشن هایی که تاکنون دیده ایم بر روی انیمیشن ساخته شده اند. دو نوع Animation وجود دارد: TargetBasedAnimation و DecayAnimation .

Animation فقط باید برای کنترل دستی زمان انیمیشن استفاده شود. Animation بدون حالت است و هیچ مفهومی از چرخه حیات ندارد. این به عنوان یک موتور محاسبه انیمیشن عمل می کند که API های سطح بالاتر از آن استفاده می کنند.

TargetBasedAnimation

سایر API ها بیشتر موارد استفاده را پوشش می دهند، اما استفاده از TargetBasedAnimation به طور مستقیم به شما امکان می دهد زمان پخش انیمیشن را خودتان کنترل کنید. در مثال زیر، زمان پخش TargetAnimation به صورت دستی بر اساس زمان فریم ارائه شده توسط withFrameNanos کنترل می شود.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

برخلاف TargetBasedAnimation ، DecayAnimation نیازی به ارائه targetValue ندارد. درعوض، targetValue خود را بر اساس شرایط شروع محاسبه می‌کند، که توسط initialVelocity و initialValue و DecayAnimationSpec ارائه شده است.

انیمیشن‌های Decay اغلب پس از حرکت حرکتی برای کاهش سرعت عناصر تا توقف استفاده می‌شوند. سرعت انیمیشن از مقدار تعیین شده توسط initialVelocityVector شروع می شود و با گذشت زمان کند می شود.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% endverbatim %}،

با animate*AsState یک مقدار را متحرک کنید

توابع animate*AsState ساده ترین APIهای انیمیشن در Compose برای متحرک کردن یک مقدار واحد هستند. شما فقط مقدار هدف (یا مقدار پایان) را ارائه می دهید و API انیمیشن را از مقدار فعلی به مقدار مشخص شده شروع می کند.

در زیر نمونه ای از متحرک سازی آلفا با استفاده از این API آورده شده است. به سادگی با قرار دادن مقدار هدف در animateFloatAsState ، مقدار آلفا اکنون یک مقدار انیمیشن بین مقادیر ارائه شده است (در این مورد 1f یا 0.5f ).

var enabled by remember { mutableStateOf(true) }

val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)

توجه داشته باشید که نیازی به ایجاد یک نمونه از کلاس انیمیشن یا مدیریت وقفه ندارید. در زیر هود، یک شی انیمیشن (یعنی یک نمونه Animatable ) ایجاد می شود و در سایت فراخوانی به خاطر سپرده می شود، با اولین مقدار هدف به عنوان مقدار اولیه آن. از آنجا به بعد، هر زمان که مقدار هدف متفاوتی را به این قابل ترکیب بدهید، یک انیمیشن به طور خودکار به سمت آن مقدار شروع می شود. اگر از قبل یک انیمیشن در حال پرواز وجود داشته باشد، انیمیشن از مقدار (و سرعت) فعلی شروع می شود و به سمت مقدار هدف حرکت می کند. در طول انیمیشن، این قابل ترکیب دوباره ترکیب می شود و هر فریم یک مقدار انیمیشن به روز شده را برمی گرداند.

خارج از جعبه، Compose توابع animate*AsState را برای Float ، Color ، Dp ، Size ، Offset ، Rect ، Int ، IntOffset و IntSize فراهم می کند. شما به راحتی می توانید با ارائه یک TwoWayConverter به animateValueAsState که نوع عمومی را می گیرد، پشتیبانی از انواع داده های دیگر اضافه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

چندین ویژگی را به طور همزمان با یک انتقال متحرک کنید

Transition یک یا چند انیمیشن را به عنوان فرزند خود مدیریت می کند و آنها را به طور همزمان بین چندین حالت اجرا می کند.

حالت ها می توانند از هر نوع داده ای باشند. در بسیاری از موارد، می توانید از یک نوع enum سفارشی برای اطمینان از ایمنی نوع استفاده کنید، مانند این مثال:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition یک نمونه از Transition را ایجاد و به خاطر می آورد و وضعیت آن را به روز می کند.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

سپس می توانید از یکی از توابع پسوند animate* برای تعریف یک انیمیشن فرزند در این انتقال استفاده کنید. مقادیر هدف را برای هر یک از حالت ها مشخص کنید. این توابع animate* یک مقدار انیمیشن را برمی‌گردانند که در طول انیمیشن زمانی که وضعیت انتقال با updateTransition به‌روزرسانی می‌شود، در هر فریم به‌روزرسانی می‌شود.

val rect by transition.animateRect(label = "rectangle") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "border width") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

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

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

هنگامی که یک انتقال به حالت هدف رسید، Transition.currentState مانند Transition.targetState خواهد بود. این می تواند به عنوان یک سیگنال برای اینکه آیا انتقال به پایان رسیده است استفاده می شود.

ما گاهی می خواهیم یک حالت اولیه متفاوت از حالت هدف اول داشته باشیم. برای رسیدن به این هدف می توانیم updateTransition با MutableTransitionState استفاده کنیم. به عنوان مثال، به ما این امکان را می دهد که به محض ورود کد به ترکیب، انیمیشن را شروع کنیم.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

برای انتقال پیچیده‌تر که شامل چندین تابع قابل ترکیب است، می‌توانید از createChildTransition برای ایجاد یک انتقال فرزند استفاده کنید. این تکنیک برای جداسازی نگرانی‌ها در میان اجزای فرعی متعدد در یک ترکیب پیچیده مفید است. انتقال والد از تمام مقادیر انیمیشن در انتقال فرزند آگاه خواهد بود.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

از انتقال با AnimatedVisibility و AnimatedContent استفاده کنید

AnimatedVisibility و AnimatedContent به عنوان توابع پسوند Transition در دسترس هستند. targetState برای Transition.AnimatedVisibility و Transition.AnimatedContent از Transition مشتق شده است و هنگامی که targetState Transition تغییر کرده است، در صورت نیاز، انتقالات ورود/خروج را آغاز می کند. این توابع افزودنی به همه انیمیشن‌های enter/exit/sizeTransform که در غیر این صورت داخلی AnimatedVisibility / AnimatedContent هستند اجازه می‌دهند تا در Transition قرار بگیرند. با این توابع افزودنی، تغییر حالت AnimatedVisibility / AnimatedContent را می توان از خارج مشاهده کرد. به جای یک پارامتر visible بولی، این نسخه از AnimatedVisibility یک لامبدا می گیرد که حالت هدف انتقال والد را به یک بولی تبدیل می کند.

برای جزئیات بیشتر به AnimatedVisibility و AnimatedContent مراجعه کنید.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

یک انتقال را کپسوله کنید و آن را قابل استفاده مجدد کنید

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

شما می توانید این کار را با ایجاد یک کلاس که تمام مقادیر انیمیشن را در خود نگه می دارد و یک تابع 'update' که نمونه ای از آن کلاس را برمی گرداند، انجام دهید. پیاده سازی انتقال را می توان در تابع جداگانه جدید استخراج کرد. این الگو زمانی مفید است که نیاز به متمرکز کردن منطق انیمیشن یا ساخت انیمیشن های پیچیده قابل استفاده مجدد باشد.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

با rememberInfiniteTransition یک انیمیشن بی نهایت تکراری ایجاد کنید

InfiniteTransition دارای یک یا چند انیمیشن کودک مانند Transition است، اما انیمیشن ها به محض ورود به ترکیب شروع به اجرا می کنند و متوقف نمی شوند مگر اینکه حذف شوند. می توانید یک نمونه از InfiniteTransition با rememberInfiniteTransition ایجاد کنید. انیمیشن های کودک را می توان با animateColor ، animatedFloat ، یا animatedValue اضافه کرد. همچنین برای تعیین مشخصات انیمیشن باید یک infiniteRepeatable را مشخص کنید.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

API های انیمیشن سطح پایین

تمام API های انیمیشن سطح بالا که در بخش قبل ذکر شد، بر روی پایه API های انیمیشن سطح پایین ساخته شده اند.

توابع animate*AsState ساده ترین API ها هستند که تغییر مقدار آنی را به عنوان یک مقدار انیمیشن ارائه می کنند. توسط Animatable پشتیبانی می شود، که یک API مبتنی بر کوروتین برای متحرک سازی یک مقدار واحد است. updateTransition یک شی انتقال ایجاد می کند که می تواند چندین مقدار متحرک را مدیریت کند و آنها را بر اساس تغییر حالت اجرا کند. rememberInfiniteTransition مشابه است، اما یک انتقال بی نهایت ایجاد می کند که می تواند چندین انیمیشن را مدیریت کند که به طور نامحدود در حال اجرا هستند. همه این APIها قابل ترکیب هستند به جز Animatable ، به این معنی که این انیمیشن ها را می توان خارج از ترکیب ایجاد کرد.

همه این APIها بر اساس API اساسی‌تر Animation هستند. اگرچه بیشتر برنامه‌ها مستقیماً با Animation تعامل ندارند، برخی از قابلیت‌های سفارشی‌سازی Animation از طریق APIهای سطح بالاتر در دسترس هستند. برای اطلاعات بیشتر در مورد AnimationVector و AnimationSpec به سفارشی کردن انیمیشن ها مراجعه کنید.

نموداری که رابطه بین API های مختلف انیمیشن سطح پایین را نشان می دهد

Animatable : انیمیشن تک ارزشی مبتنی بر Coroutine

Animatable یک دارنده مقدار است که می تواند مقدار را با تغییر آن از طریق animateTo متحرک کند. این API پشتیبان‌گیری از اجرای animate*AsState است. این تداوم مداوم و انحصار متقابل را تضمین می کند، به این معنی که تغییر ارزش همیشه مداوم است و هر انیمیشن در حال انجام لغو خواهد شد.

بسیاری از ویژگی های Animatable ، از جمله animateTo ، به عنوان توابع تعلیق ارائه شده است. این بدان معنی است که آنها باید در یک محدوده کوروتین مناسب پیچیده شوند. برای مثال، می‌توانید از ترکیب‌کننده LaunchedEffect برای ایجاد یک محدوده فقط برای مدت زمان مقدار کلید مشخص شده استفاده کنید.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

در مثال بالا، یک نمونه از Animatable با مقدار اولیه Color.Gray را ایجاد کرده و به خاطر می آوریم. بسته به مقدار پرچم بولی ok ، رنگ به رنگ Color.Green یا Color.Red متحرک می شود. هر تغییر بعدی در مقدار بولی باعث شروع انیمیشن به رنگ دیگر می شود. اگر هنگام تغییر مقدار، انیمیشنی در حال انجام باشد، انیمیشن لغو می‌شود و انیمیشن جدید از مقدار عکس فوری فعلی با سرعت فعلی شروع می‌شود.

این پیاده‌سازی انیمیشنی است که از animate*AsState API ذکر شده در بخش قبل پشتیبانی می‌کند. در مقایسه با animate*AsState ، استفاده از Animatable مستقیماً از چندین جنبه به ما کنترل دقیق تری می دهد. اول، Animatable می تواند یک مقدار اولیه متفاوت از اولین مقدار هدف خود داشته باشد. به عنوان مثال، مثال کد بالا در ابتدا یک کادر خاکستری را نشان می دهد که بلافاصله شروع به متحرک شدن به رنگ سبز یا قرمز می کند. دوم، Animatable عملیات بیشتری را روی مقدار محتوا، یعنی snapTo و animateDecay ارائه می‌کند. snapTo مقدار فعلی را بلافاصله به مقدار هدف تنظیم می کند. این زمانی مفید است که خود انیمیشن تنها منبع حقیقت نیست و باید با سایر حالت‌ها، مانند رویدادهای لمسی، همگام شود. animateDecay انیمیشنی را شروع می کند که از سرعت داده شده کاهش می یابد. این برای اجرای رفتار پرت کردن مفید است. برای اطلاعات بیشتر به ژست و انیمیشن مراجعه کنید.

خارج از جعبه، Animatable از Float و Color پشتیبانی می کند، اما هر نوع داده ای را می توان با ارائه یک TwoWayConverter استفاده کرد. برای اطلاعات بیشتر به AnimationVector مراجعه کنید.

شما می توانید مشخصات انیمیشن را با ارائه AnimationSpec شخصی سازی کنید. برای اطلاعات بیشتر به AnimationSpec مراجعه کنید.

Animation : انیمیشن با کنترل دستی

Animation پایین‌ترین API انیمیشن موجود است. بسیاری از انیمیشن هایی که تاکنون دیده ایم بر روی انیمیشن ساخته شده اند. دو نوع Animation وجود دارد: TargetBasedAnimation و DecayAnimation .

Animation فقط باید برای کنترل دستی زمان انیمیشن استفاده شود. Animation بدون حالت است و هیچ مفهومی از چرخه حیات ندارد. این به عنوان یک موتور محاسبه انیمیشن عمل می کند که API های سطح بالاتر از آن استفاده می کنند.

TargetBasedAnimation

سایر API ها بیشتر موارد استفاده را پوشش می دهند، اما استفاده از TargetBasedAnimation به طور مستقیم به شما امکان می دهد زمان پخش انیمیشن را خودتان کنترل کنید. در مثال زیر، زمان پخش TargetAnimation به صورت دستی بر اساس زمان فریم ارائه شده توسط withFrameNanos کنترل می شود.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

برخلاف TargetBasedAnimation ، DecayAnimation نیازی به ارائه targetValue ندارد. درعوض، targetValue خود را بر اساس شرایط شروع محاسبه می‌کند، که توسط initialVelocity و initialValue و DecayAnimationSpec ارائه شده است.

انیمیشن‌های Decay اغلب پس از حرکت حرکتی برای کاهش سرعت عناصر تا توقف استفاده می‌شوند. سرعت انیمیشن از مقدار تعیین شده توسط initialVelocityVector شروع می شود و با گذشت زمان کند می شود.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% endverbatim %}،

با animate*AsState یک مقدار را متحرک کنید

توابع animate*AsState ساده ترین APIهای انیمیشن در Compose برای متحرک کردن یک مقدار واحد هستند. شما فقط مقدار هدف (یا مقدار پایان) را ارائه می دهید و API انیمیشن را از مقدار فعلی به مقدار مشخص شده شروع می کند.

در زیر نمونه ای از متحرک سازی آلفا با استفاده از این API آورده شده است. به سادگی با قرار دادن مقدار هدف در animateFloatAsState ، مقدار آلفا اکنون یک مقدار انیمیشن بین مقادیر ارائه شده است (در این مورد 1f یا 0.5f ).

var enabled by remember { mutableStateOf(true) }

val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)

توجه داشته باشید که نیازی به ایجاد یک نمونه از کلاس انیمیشن یا مدیریت وقفه ندارید. در زیر هود، یک شی انیمیشن (یعنی یک نمونه Animatable ) ایجاد می شود و در سایت فراخوانی به خاطر سپرده می شود، با اولین مقدار هدف به عنوان مقدار اولیه آن. از آنجا به بعد، هر زمان که مقدار هدف متفاوتی را به این قابل ترکیب بدهید، یک انیمیشن به طور خودکار به سمت آن مقدار شروع می شود. اگر از قبل یک انیمیشن در حال پرواز وجود داشته باشد، انیمیشن از مقدار (و سرعت) فعلی شروع می شود و به سمت مقدار هدف حرکت می کند. در طول انیمیشن، این قابل ترکیب دوباره ترکیب می شود و هر فریم یک مقدار انیمیشن به روز شده را برمی گرداند.

خارج از جعبه، Compose توابع animate*AsState را برای Float ، Color ، Dp ، Size ، Offset ، Rect ، Int ، IntOffset و IntSize فراهم می کند. با تهیه یک TwoWayConverter به animateValueAsState که نوع عمومی را می گیرد ، می توانید به راحتی از انواع داده های دیگر پشتیبانی کنید.

می توانید مشخصات انیمیشن را با تهیه یک AnimationSpec سفارشی کنید. برای اطلاعات بیشتر به AnimationsPec مراجعه کنید.

چندین ویژگی را همزمان با یک انتقال تحریک کنید

Transition یک یا چند انیمیشن را به عنوان فرزندان خود مدیریت می کند و آنها را همزمان بین چندین حالت اداره می کند.

ایالات می توانند از هر نوع داده ای باشند. در بسیاری از موارد ، شما می توانید از نوع enum سفارشی برای اطمینان از ایمنی نوع ، مانند این مثال استفاده کنید:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition نمونه ای از Transition ایجاد و به یاد می آورد و وضعیت خود را به روز می کند.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

سپس می توانید از یکی از توابع پسوند animate* برای تعریف انیمیشن کودک در این انتقال استفاده کنید. مقادیر هدف را برای هر یک از ایالات مشخص کنید. این توابع animate* مقدار انیمیشن را باز می گردانند که در هنگام انیمیشن هنگام به روزرسانی با updateTransition ، هر فریم را در طول انیمیشن به روز می کند.

val rect by transition.animateRect(label = "rectangle") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "border width") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

به صورت اختیاری ، می توانید یک پارامتر transitionSpec را برای مشخص کردن یک AnimationSpec مختلف برای هر یک از ترکیبات تغییرات حالت انتقال منتقل کنید. برای اطلاعات بیشتر به AnimationsPec مراجعه کنید.

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

هنگامی که یک انتقال به حالت هدف رسید ، Transition.currentState همان Transition.targetState خواهد بود. این می تواند به عنوان سیگنالی برای اتمام انتقال استفاده شود.

ما گاهی اوقات می خواهیم یک حالت اولیه متفاوت از حالت هدف اول داشته باشیم. ما می توانیم برای دستیابی به این هدف updateTransition با MutableTransitionState استفاده کنیم. به عنوان مثال ، به ما این امکان را می دهد تا به محض ورود کد ، انیمیشن را شروع کنیم.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

برای یک انتقال پیچیده تر که شامل چندین کارکرد سازنده است ، می توانید از createChildTransition برای ایجاد یک انتقال کودک استفاده کنید. این تکنیک برای جدا کردن نگرانی ها در بین چندین مؤلفه در یک ترکیب پیچیده مفید است. انتقال والدین از تمام ارزشهای انیمیشن در انتقال کودک آگاه خواهد بود.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

از انتقال با AnimatedVisibility و AnimatedContent استفاده کنید

AnimatedVisibility و AnimatedContent به عنوان توابع فرمت Transition در دسترس هستند. targetState برای Transition.AnimatedVisibility و Transition.AnimatedContent از Transition گرفته می شود ، و در صورت نیاز به تغییر Transition targetState انتقال ENTER/EXIT را در صورت لزوم تحریک می کند. این توابع پسوند به تمام انیمیشن های Enter/Exit/Sizetransform اجازه می دهد که در غیر این صورت داخلی برای AnimatedVisibility / AnimatedContent در حال Transition باشد. با استفاده از این توابع پسوند ، تغییر حالت AnimatedVisibility / AnimatedContent می تواند از خارج مشاهده شود. به جای یک پارامتر visible Boolean ، این نسخه از AnimatedVisibility یک لامبدا را می گیرد که حالت هدف انتقال والدین را به یک بولی تبدیل می کند.

برای جزئیات بیشتر به AnimatedIsisibility و AnimatedContent مراجعه کنید.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

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

برای موارد استفاده ساده ، تعریف انیمیشن های گذار در همان ترکیب قابل استفاده در UI شما یک گزینه کاملاً معتبر است. هنگامی که شما روی یک مؤلفه پیچیده با تعدادی از مقادیر انیمیشن کار می کنید ، ممکن است بخواهید اجرای انیمیشن را از UI کامپوزیت جدا کنید.

شما می توانید این کار را با ایجاد کلاس انجام دهید که تمام مقادیر انیمیشن و یک عملکرد "به روزرسانی" را داشته باشد که نمونه ای از آن کلاس را برمی گرداند. اجرای انتقال را می توان به عملکرد جداگانه جدید استخراج کرد. این الگوی در صورت نیاز به متمرکز کردن منطق انیمیشن یا ایجاد انیمیشن های پیچیده قابل استفاده مجدد مفید است.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

یک انیمیشن بی نهایت تکراری با rememberInfiniteTransition ایجاد کنید

InfiniteTransition یک یا چند انیمیشن کودک مانند Transition را در خود جای داده است ، اما انیمیشن ها به محض ورود به ترکیب شروع می شوند و متوقف نمی شوند مگر اینکه حذف شوند. شما می توانید نمونه ای از InfiniteTransition با rememberInfiniteTransition به یاد داشته باشید. انیمیشن های کودک را می توان با animateColor ، animatedFloat یا animatedValue اضافه کرد. همچنین برای مشخص کردن مشخصات انیمیشن باید یک infiniterepeatable را مشخص کنید.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

API های انیمیشن سطح پایین

تمام API های انیمیشن سطح بالا ذکر شده در بخش قبلی در بالای پایه API های انیمیشن سطح پایین ساخته شده اند.

توابع animate*AsState ساده ترین API ها هستند که به عنوان یک مقدار انیمیشن تغییر ارزش فوری را ایجاد می کنند. این پشتیبانی توسط Animatable ، که یک API مبتنی بر Coroutine برای متحرک یک مقدار واحد است ، پشتیبانی می شود. updateTransition یک شیء انتقال ایجاد می کند که می تواند چندین مقادیر انیمیشن را مدیریت کند و بر اساس تغییر حالت آنها را اجرا کند. rememberInfiniteTransition مشابه است ، اما یک انتقال نامحدود ایجاد می کند که می تواند چندین انیمیشن را مدیریت کند که به طور نامحدود در حال اجرا هستند. همه این API ها به جز Animatable ترکیبات هستند ، به این معنی که این انیمیشن ها می توانند در خارج از ترکیب ایجاد شوند.

همه این API ها بر اساس API Animation اساسی تر است. اگرچه بیشتر برنامه ها مستقیماً با Animation تعامل نخواهند داشت ، اما برخی از قابلیت های سفارشی سازی برای Animation از طریق API های سطح بالاتر در دسترس هستند. برای اطلاعات بیشتر در مورد AnimationVector و AnimationSpec به انیمیشن های سفارشی مراجعه کنید.

نمودار نشان می دهد رابطه بین API های مختلف انیمیشن سطح پایین

Animatable : انیمیشن با ارزش تک مبتنی بر Coroutine

Animatable یک دارنده مقدار است که می تواند مقدار را با تغییر آن از طریق animateTo متحرک کند. این API پشتیبان‌گیری از اجرای animate*AsState است. این ادامه مداوم و انحصار متقابل را تضمین می کند ، به این معنی که تغییر ارزش همیشه مداوم است و هر انیمیشن مداوم لغو می شود.

بسیاری از ویژگی های Animatable ، از جمله animateTo ، به عنوان توابع معلق ارائه شده است. این بدان معنی است که آنها باید در یک محدوده مناسب Coroutine پیچیده شوند. به عنوان مثال ، شما می توانید از Composive LaunchedEffect برای ایجاد دامنه صرفاً برای مدت زمان کلیده مشخص شده استفاده کنید.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

در مثال بالا ، نمونه ای از Animatable را با مقدار اولیه Color.Gray ایجاد و به خاطر می آوریم. بسته به ارزش پرچم ok ، رنگ Color.Red Color.Green هرگونه تغییر بعدی به مقدار بولی ، انیمیشن را به رنگ دیگر شروع می کند. اگر یک انیمیشن مداوم در هنگام تغییر مقدار وجود داشته باشد ، انیمیشن لغو می شود و انیمیشن جدید از مقدار عکس فوری فعلی با سرعت فعلی شروع می شود.

این اجرای انیمیشن است که از animate*AsState API که در بخش قبلی ذکر شده است ، حمایت می کند. در مقایسه با animate*AsState ، استفاده از Animatable به طور مستقیم کنترل ریز و درشت را از چندین جنبه به ما می دهد. اول ، Animatable می تواند یک مقدار اولیه متفاوت از اولین مقدار هدف خود داشته باشد. به عنوان مثال ، مثال کد در بالا در ابتدا یک جعبه خاکستری را نشان می دهد ، که بلافاصله شروع به انیمیشن به رنگ سبز یا قرمز می کند. دوم ، Animatable عملیات بیشتری را در مورد مقدار محتوا ، یعنی snapTo و animateDecay ارائه می دهد. snapTo مقدار فعلی را بلافاصله به مقدار هدف تنظیم می کند. این زمانی مفید است که خود انیمیشن تنها منبع حقیقت نیست و باید با سایر کشورها مانند رویدادهای لمسی همگام سازی شود. animateDecay یک انیمیشن را شروع می کند که از سرعت داده شده کند می شود. این برای اجرای رفتار fling مفید است. برای اطلاعات بیشتر به ژست و انیمیشن مراجعه کنید.

خارج از جعبه ، Animatable از Float و Color پشتیبانی می کند ، اما با تهیه یک TwoWayConverter می توان از هر نوع داده استفاده کرد. برای اطلاعات بیشتر به AnimationVector مراجعه کنید.

می توانید مشخصات انیمیشن را با تهیه یک AnimationSpec سفارشی کنید. برای اطلاعات بیشتر به AnimationsPec مراجعه کنید.

Animation : انیمیشن کنترل شده دستی

Animation پایین ترین سطح انیمیشن API موجود است. بسیاری از انیمیشن هایی که تاکنون دیده ایم در حال ساخت انیمیشن است. دو زیرگروه Animation وجود دارد: TargetBasedAnimation و DecayAnimation .

Animation فقط باید برای کنترل دستی زمان انیمیشن استفاده شود. Animation بدون تابعیت است و هیچ مفهومی از چرخه حیات ندارد. این موتور به عنوان یک موتور محاسبه انیمیشن است که API های سطح بالاتر از آن استفاده می کنند.

TargetBasedAnimation

سایر API ها بیشتر موارد استفاده را پوشش می دهند ، اما استفاده از TargetBasedAnimation به طور مستقیم به شما امکان می دهد زمان بازی انیمیشن را خودتان کنترل کنید. در مثال زیر ، زمان بازی TargetAnimation بر اساس زمان فریم ارائه شده توسط withFrameNanos به صورت دستی کنترل می شود.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

بر خلاف TargetBasedAnimation ، DecayAnimation نیازی به ارائه یک targetValue ندارد. درعوض ، آن را بر اساس شرایط شروع ، تعیین شده توسط initialVelocity و initialValue و DecayAnimationSpec ، محاسبه targetValue کند.

انیمیشن های پوسیدگی اغلب پس از یک حرکت پرش استفاده می شوند تا عناصر را به سرعت درآورد. سرعت انیمیشن از مقدار تعیین شده توسط initialVelocityVector شروع می شود و با گذشت زمان کند می شود.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}