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

با 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 شروع می شود و با گذشت زمان کند می شود.

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