این صفحه نحوه ایجاد انیمیشنهای مبتنی بر مقدار در Jetpack Compose را شرح میدهد و بر APIهایی تمرکز دارد که مقادیر را بر اساس حالتهای فعلی و هدفشان متحرکسازی میکنند.
متحرکسازی یک مقدار واحد با animate*AsState
توابع animate*AsState
، APIهای انیمیشن سرراستی در Compose برای متحرکسازی یک مقدار واحد هستند. شما فقط مقدار هدف (یا مقدار پایانی) را ارائه میدهید و API انیمیشن را از مقدار فعلی تا مقدار مشخص شده شروع میکند.
مثال زیر با استفاده از این API، آلفا را متحرک میکند. با قرار دادن مقدار هدف در animateFloatAsState
، مقدار آلفا اکنون یک مقدار انیمیشن بین مقادیر ارائه شده است (در این مورد 1f
یا 0.5f
).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
نیازی به ایجاد نمونهای از هیچ کلاس انیمیشن یا مدیریت وقفهای ندارید. در پشت صحنه، یک شیء انیمیشن (یعنی یک نمونه Animatable
) ایجاد و در محل فراخوانی به خاطر سپرده میشود، با اولین مقدار هدف به عنوان مقدار اولیه آن. از آنجا به بعد، هر زمان که به این composable مقدار هدف متفاوتی بدهید، یک انیمیشن به طور خودکار به سمت آن مقدار شروع میشود. اگر از قبل انیمیشنی در حال اجرا باشد، انیمیشن از مقدار فعلی (و سرعت) خود شروع میشود و به سمت مقدار هدف حرکت میکند. در طول انیمیشن، این composable دوباره ترکیب میشود و در هر فریم یک مقدار انیمیشن بهروز شده را برمیگرداند.
به طور پیشفرض، 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
مشتق شده است و انیمیشنهای enter، exit و sizeTransform
را در صورت نیاز، زمانی که targetState
مربوط به Transition
تغییر میکند، فعال میکند. این توابع افزونه به شما امکان میدهند تمام انیمیشنهای enter، exit و sizeTransform
را که در غیر این صورت درون AnimatedVisibility
/ AnimatedContent
قرار میگرفتند، به Transition
منتقل کنید. با این توابع افزونه، میتوانید تغییر وضعیت AnimatedVisibility
/ AnimatedContent
را از بیرون مشاهده کنید. به جای یک پارامتر boolean visible
، این نسخه از AnimatedVisibility
یک لامبدا میگیرد که وضعیت هدف گذار والد را به boolean تبدیل میکند.
برای جزئیات بیشتر به 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
را در خود جای میدهد، اما انیمیشنها به محض ورود به ترکیب شروع به اجرا میکنند و تا زمانی که حذف نشوند، متوقف نمیشوند. میتوانید با rememberInfiniteTransition
یک نمونه از InfiniteTransition
ایجاد کنید و انیمیشنهای فرزند را با 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
تعامل ندارند، اما میتوانید از طریق APIهای سطح بالاتر به برخی از قابلیتهای سفارشیسازی آن دسترسی داشته باشید. برای اطلاعات بیشتر در مورد AnimationVector
و AnimationSpec
به بخش Customize animations مراجعه کنید.
Animatable
: انیمیشن تک مقداری مبتنی بر Coroutine
Animatable
یک نگهدارنده مقدار است که میتواند مقدار را هنگام تغییر با استفاده از animateTo
متحرک کند. این API است که از پیادهسازی animate*AsState
پشتیبانی میکند. این API تداوم سازگار و انحصار متقابل را تضمین میکند، به این معنی که تغییر مقدار همیشه پیوسته است و Compose هرگونه انیمیشن در حال انجام را لغو میکند.
بسیاری از ویژگیهای Animatable
، از جمله animateTo
، توابع suspend هستند. این بدان معناست که شما باید آنها را در یک محدوده کوروتین مناسب قرار دهید. برای مثال، میتوانید از LaunchedEffect
composable برای ایجاد یک محدوده فقط برای مدت زمان مقدار کلید مشخص شده استفاده کنید.
// 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
تغییر میکند. هرگونه تغییر بعدی در مقدار بولی، انیمیشنی را با رنگ دیگر شروع میکند. اگر هنگام تغییر مقدار، انیمیشنی در حال انجام باشد، Compose انیمیشن را لغو میکند و انیمیشن جدید از مقدار snapshot فعلی با سرعت فعلی شروع میشود.
این API Animatable
، پیادهسازی اساسی برای animate*AsState
است که در بخش قبلی به آن اشاره شد. استفاده مستقیم از Animatable
کنترل دقیقتری را از چندین طریق ارائه میدهد:
- اول اینکه،
Animatable
میتواند مقدار اولیهای متفاوت از اولین مقدار هدف خود داشته باشد. برای مثال، مثال کد قبلی در ابتدا یک جعبه خاکستری را نشان میدهد که بلافاصله به رنگ سبز یا قرمز تغییر رنگ میدهد. - دوم،
Animatable
عملیات بیشتری روی مقدار محتوا، به ویژهsnapTo
وanimateDecay
، ارائه میدهد.-
snapTo
مقدار فعلی را فوراً به مقدار هدف تنظیم میکند. این زمانی مفید است که انیمیشن تنها منبع حقیقت نباشد و باید با حالتهای دیگر، مانند رویدادهای لمسی، همگامسازی شود. -
animateDecay
انیمیشنی را شروع میکند که از سرعت داده شده کندتر میشود. این برای پیادهسازی رفتار fling مفید است.
-
برای اطلاعات بیشتر به بخش ژست و انیمیشن مراجعه کنید.
به طور پیشفرض، Animatable
Float
و Color
پشتیبانی میکند، اما میتوانید با ارائه TwoWayConverter
از هر نوع دادهای استفاده کنید. برای اطلاعات بیشتر به AnimationVector مراجعه کنید.
شما میتوانید مشخصات انیمیشن را با ارائه AnimationSpec
سفارشی کنید. برای اطلاعات بیشتر به AnimationSpec
مراجعه کنید.
Animation
: انیمیشن کنترل دستی
Animation
پایینترین سطح API انیمیشن موجود است. بسیاری از انیمیشنهایی که تاکنون دیدهایم بر اساس Animation
ساخته شدهاند. دو زیرگروه 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 اغلب پس از یک حرکت fling برای کند کردن عناصر تا توقف استفاده میشوند. سرعت انیمیشن از مقداری که initialVelocityVector
تعیین میکند شروع میشود و به مرور زمان کندتر میشود.
برای شما توصیه میشود
- توجه: متن لینک زمانی نمایش داده میشود که جاوا اسکریپت غیرفعال باشد.
- سفارشیسازی انیمیشنها
- انیمیشنها در نوشتن
- اصلاحکنندهها و ترکیبکنندههای انیمیشن