ภาพเคลื่อนไหวตามมูลค่า

เคลื่อนไหวค่าเดียวด้วย 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 นี้ ระบบจะเริ่มภาพเคลื่อนไหวไปยังค่าดังกล่าวโดยอัตโนมัติ หากมีภาพเคลื่อนไหวที่กำลังทำงานอยู่ ภาพเคลื่อนไหวจะเริ่มจาก ค่าปัจจุบัน (และความเร็ว) และเคลื่อนไหวไปยังค่าเป้าหมาย ในระหว่าง ภาพเคลื่อนไหว ระบบจะทำการประกอบฟังก์ชันที่ประกอบกันได้นี้อีกครั้งและแสดงค่าภาพเคลื่อนไหวที่อัปเดต ทุกเฟรม

Compose มีฟังก์ชัน animate*AsState สำหรับ Float, Color, Dp, Size, Offset, Rect, Int, IntOffset และ IntSize โดยค่าเริ่มต้น คุณเพิ่มการรองรับข้อมูลประเภทอื่นๆ ได้อย่างง่ายดายโดยระบุTwoWayConverterไปยัง animateValueAsState ที่ใช้ประเภททั่วไป

คุณปรับแต่งข้อกำหนดของภาพเคลื่อนไหวได้โดยระบุ AnimationSpec ดูข้อมูลเพิ่มเติมได้ที่ AnimationSpec

สร้างภาพเคลื่อนไหวพร็อพเพอร์ตี้หลายรายการพร้อมกันด้วยการเปลี่ยน

Transition จัดการภาพเคลื่อนไหวอย่างน้อย 1 รายการเป็นองค์ประกอบย่อยและเรียกใช้พร้อมกันระหว่างหลายสถานะ

สถานะอาจเป็นประเภทข้อมูลใดก็ได้ ในหลายกรณี คุณสามารถใช้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") // ……

สำหรับการเปลี่ยนที่ซับซ้อนมากขึ้นซึ่งเกี่ยวข้องกับฟังก์ชัน Composable หลายรายการ คุณสามารถ ใช้ createChildTransition เพื่อสร้างการเปลี่ยนระดับย่อยได้ เทคนิคนี้มีประโยชน์ในการแยกความกังวล ในหมู่คอมโพเนนต์ย่อยหลายรายการใน Composable ที่ซับซ้อน การเปลี่ยนฉากหลักจะ รับรู้ค่าภาพเคลื่อนไหวทั้งหมดในการเปลี่ยนฉากย่อย

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 เปลี่ยนแปลง ฟังก์ชันส่วนขยายเหล่านี้ช่วยให้แอนิเมชันการเข้า/ออก/sizeTransform ทั้งหมดที่ปกติจะอยู่ภายใน AnimatedVisibility/AnimatedContent ถูกย้ายไปไว้ใน Transition ฟังก์ชันส่วนขยายเหล่านี้ช่วยให้สังเกตการเปลี่ยนแปลงสถานะของ AnimatedVisibility/AnimatedContent จากภายนอกได้ AnimatedVisibility เวอร์ชันนี้ใช้ Lambda ที่แปลงสถานะเป้าหมายของการเปลี่ยน สถานะหลักเป็นบูลีนแทนvisibleพารามิเตอร์บูลีน

ดูรายละเอียดได้ที่ 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")             }         }     } }

ห่อหุ้มการเปลี่ยนฉากและทำให้ใช้ซ้ำได้

สำหรับกรณีการใช้งานที่เรียบง่าย การกำหนดภาพเคลื่อนไหวของการเปลี่ยนฉากใน Composable เดียวกันกับ 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 มีภาพเคลื่อนไหวขององค์ประกอบย่อยอย่างน้อย 1 รายการ เช่น 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 ทั้งหมดนี้อิงตาม Animation API ที่เป็นพื้นฐานมากกว่า แม้ว่าแอปส่วนใหญ่จะไม่โต้ตอบกับ Animation โดยตรง แต่ความสามารถในการปรับแต่งบางอย่างสำหรับ Animation จะพร้อมใช้งานผ่าน API ระดับสูงกว่า ดูข้อมูลเพิ่มเติมเกี่ยวกับ AnimationVector และ AnimationSpec ได้ที่ ปรับแต่งภาพเคลื่อนไหว

แผนภาพแสดงความสัมพันธ์ระหว่าง API การเคลื่อนไหวระดับต่ำต่างๆ

Animatable: ภาพเคลื่อนไหวค่าเดียวที่อิงตามโครูทีน

Animatable คือตัวยึดค่าที่สามารถเคลื่อนไหวค่าได้เมื่อมีการเปลี่ยนแปลงผ่าน animateTo นี่คือ API ที่สนับสนุนการใช้งาน animate*AsState ซึ่งจะช่วยให้มั่นใจได้ถึงการดำเนินการอย่างต่อเนื่องและไม่ทับซ้อนกัน ซึ่งหมายความว่า การเปลี่ยนแปลงค่าจะต่อเนื่องเสมอและระบบจะยกเลิกภาพเคลื่อนไหวที่กำลังดำเนินการ

ฟีเจอร์หลายอย่างของ Animatable รวมถึง animateTo จะมีสถานะเป็นฟังก์ชันที่ถูกระงับ ซึ่งหมายความว่าต้องห่อหุ้มด้วยขอบเขตของโครูทีนที่เหมาะสม เช่น คุณสามารถใช้ LaunchedEffectComposable เพื่อสร้างขอบเขตเฉพาะระยะเวลาของคีย์-ค่าที่ระบุ

// 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 สีจะเคลื่อนไหวเป็น Color.Green หรือ Color.Red ขึ้นอยู่กับค่าของแฟล็กบูลีน ok การเปลี่ยนแปลงค่าบูลีนในภายหลัง จะเริ่มภาพเคลื่อนไหวเป็นสีอื่น หากมีภาพเคลื่อนไหวที่ กำลังดำเนินการอยู่เมื่อมีการเปลี่ยนค่า ระบบจะยกเลิกภาพเคลื่อนไหว และ ภาพเคลื่อนไหวใหม่จะเริ่มจากค่าสแนปชอตปัจจุบันด้วยความเร็วปัจจุบัน

นี่คือการติดตั้งใช้งานภาพเคลื่อนไหวที่รองรับ animate*AsState API ที่กล่าวถึงในส่วนก่อนหน้า เมื่อเทียบกับ animate*AsState การใช้ Animatable โดยตรงช่วยให้เราควบคุมได้ละเอียดยิ่งขึ้นในหลายๆ ด้าน ก่อนอื่น Animatable สามารถมีค่าเริ่มต้นที่แตกต่างจากค่าเป้าหมายแรกได้ ตัวอย่างเช่น ตัวอย่างโค้ดด้านบนแสดงกล่องสีเทาในตอนแรก ซึ่งจะเริ่มเคลื่อนไหวเป็นสีเขียวหรือสีแดงทันที ประการที่สอง Animatable มีการดำเนินการเพิ่มเติม เกี่ยวกับมูลค่าเนื้อหา ได้แก่ snapTo และ animateDecay snapTo ตั้งค่าปัจจุบันเป็นค่าเป้าหมายทันที ซึ่งมีประโยชน์เมื่อภาพเคลื่อนไหวเองไม่ใช่แหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียว และต้องซิงค์กับสถานะอื่นๆ เช่น เหตุการณ์การแตะ animateDecay เริ่มภาพเคลื่อนไหวที่ช้าลง จากความเร็วที่ระบุ ซึ่งมีประโยชน์สำหรับการใช้งานลักษณะการทำงานแบบดีด ดูข้อมูลเพิ่มเติมได้ที่ท่าทางสัมผัสและภาพเคลื่อนไหว

Animatable รองรับ Float และ Color โดยค่าเริ่มต้น แต่สามารถใช้ข้อมูลประเภทใดก็ได้โดยระบุ TwoWayConverter ดูข้อมูลเพิ่มเติมได้ที่ AnimationVector

คุณปรับแต่งข้อกำหนดของภาพเคลื่อนไหวได้โดยระบุ AnimationSpec ดูข้อมูลเพิ่มเติมได้ที่ AnimationSpec

Animation: ภาพเคลื่อนไหวที่ควบคุมด้วยตนเอง

Animation เป็น Animation API ระดับต่ำสุดที่พร้อมใช้งาน ภาพเคลื่อนไหวหลายรายการ ที่เราเห็นจนถึงตอนนี้สร้างขึ้นบน Animation Animation มี 2 ชนิดย่อย ได้แก่ TargetBasedAnimation และ DecayAnimation

Animation ควรใช้เพื่อควบคุมเวลาของภาพเคลื่อนไหวด้วยตนเองเท่านั้น Animation ไม่มีสถานะและไม่มีแนวคิดเรื่องวงจรการใช้งาน โดย ทำหน้าที่เป็นเครื่องมือคำนวณภาพเคลื่อนไหวที่ API ระดับสูงกว่าใช้

TargetBasedAnimation

API อื่นๆ ครอบคลุม Use Case ส่วนใหญ่ แต่การใช้ 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 ที่ระบุ

โดยมักใช้ภาพเคลื่อนไหวการลดลงหลังจากท่าทางสัมผัสแบบปัดเพื่อชะลอองค์ประกอบให้หยุด ความเร็วของภาพเคลื่อนไหวจะเริ่มต้นที่ค่าที่ตั้งไว้โดย initialVelocityVector และจะช้าลงเมื่อเวลาผ่านไป