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