با 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
به سفارشی کردن انیمیشن ها مراجعه کنید.
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
شروع می شود و با گذشت زمان کند می شود.
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- سفارشی کردن انیمیشن ها {:#customize-animations}
- انیمیشن ها در Compose
- اصلاحکنندهها و ترکیبکنندههای انیمیشن