หน้านี้อธิบายวิธีสร้างภาพเคลื่อนไหวตามมูลค่าใน 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 อินสแตนซ์) ที่เว็บไซต์ที่เรียกใช้ โดยมีค่าเป้าหมายแรก เป็นค่าเริ่มต้น หลังจากนั้น เมื่อใดก็ตามที่คุณระบุค่าเป้าหมายที่แตกต่างกันสำหรับคอมโพสเซเบิลนี้ ระบบจะเริ่มภาพเคลื่อนไหวไปยังค่าดังกล่าวโดยอัตโนมัติ
หากมีภาพเคลื่อนไหวที่กำลังทำงานอยู่ ภาพเคลื่อนไหวจะเริ่มจากค่าปัจจุบัน (และความเร็ว) และเคลื่อนไหวไปยังค่าเป้าหมาย ในระหว่าง
ภาพเคลื่อนไหว ระบบจะทำการประกอบฟังก์ชันที่ประกอบกันได้นี้อีกครั้งและแสดงค่าภาพเคลื่อนไหวที่อัปเดต
ทุกเฟรม
โดยค่าเริ่มต้น Compose จะมีฟังก์ชัน animate*AsState สำหรับ Float, Color,
Dp, Size, Offset, Rect, Int, IntOffset และ IntSize คุณเพิ่ม
การรองรับข้อมูลประเภทอื่นๆ ได้โดยระบุ TwoWayConverter to
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") // ……
สำหรับการเปลี่ยนที่ซับซ้อนมากขึ้นซึ่งเกี่ยวข้องกับฟังก์ชันที่ประกอบกันได้หลายรายการ คุณสามารถใช้ 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 และทริกเกอร์
การเคลื่อนไหวเข้า ออก และ sizeTransform ตามต้องการเมื่อ 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") } } } }
ห่อหุ้มการเปลี่ยนฉากและทำให้ใช้ซ้ำได้
สำหรับ Use Case ที่ตรงไปตรงมา การกำหนดภาพเคลื่อนไหวของการเปลี่ยนฉากใน Composable เดียวกันกับ UI เป็นตัวเลือกที่ใช้ได้ อย่างไรก็ตาม เมื่อทำงานกับคอมโพเนนต์ที่ซับซ้อน ซึ่งมีค่าที่เคลื่อนไหวจำนวนมาก คุณอาจต้องการแยก การใช้งานภาพเคลื่อนไหวออกจาก UI ที่ประกอบได้
คุณทำได้โดยการสร้างคลาสที่เก็บค่าภาพเคลื่อนไหวทั้งหมดและ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 มีภาพเคลื่อนไหวขององค์ประกอบย่อยอย่างน้อย 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 ได้ที่ปรับแต่ง
ภาพเคลื่อนไหว
Animatable: ภาพเคลื่อนไหวค่าเดียวที่อิงตามโครูทีน
Animatable เป็นตัวยึดค่าที่สามารถเคลื่อนไหวค่าได้เมื่อมีการเปลี่ยนแปลง
โดยใช้ animateTo นี่คือ API ที่สนับสนุนการใช้งาน
animate*AsState ซึ่งจะช่วยให้มั่นใจได้ถึงความต่อเนื่องที่สอดคล้องกันและความพิเศษร่วมกัน
ซึ่งหมายความว่าการเปลี่ยนแปลงค่าจะต่อเนื่องเสมอ และ Compose จะยกเลิกภาพเคลื่อนไหวที่
กำลังดำเนินการอยู่
ฟีเจอร์หลายอย่างของ Animatable รวมถึง animateTo เป็นฟังก์ชันระงับ
ซึ่งหมายความว่าคุณต้องรวมไว้ในขอบเขตของโครูทีนที่เหมาะสม ตัวอย่างเช่น
คุณใช้ 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 สีจะเคลื่อนไหวเป็น Color.Green หรือ Color.Red ทั้งนี้ขึ้นอยู่กับค่าของบูลีน
แฟล็ก ok การเปลี่ยนแปลงค่าบูลีนในภายหลังจะเริ่มภาพเคลื่อนไหวไปยังอีกสีหนึ่ง
หากภาพเคลื่อนไหวกำลังทำงานอยู่เมื่อค่ามีการเปลี่ยนแปลง Compose จะยกเลิก ภาพเคลื่อนไหว และภาพเคลื่อนไหวใหม่จะเริ่มจากค่าสแนปชอตปัจจุบันที่มี ความเร็วปัจจุบัน
Animatable API นี้เป็นการใช้งานพื้นฐานสำหรับ animate*AsState
ที่กล่าวถึงในส่วนก่อนหน้า การใช้ Animatable โดยตรงช่วยให้คุณควบคุมได้ละเอียดยิ่งขึ้นในหลายๆ ด้าน ดังนี้
- ก่อนอื่น
Animatableอาจมีค่าเริ่มต้นที่แตกต่างจากค่าเป้าหมายแรก เช่น ตัวอย่างโค้ดก่อนหน้าแสดงกล่องสีเทาในตอนแรก ซึ่งจะเปลี่ยนเป็นสีเขียวหรือสีแดงทันที - ประการที่สอง
Animatableมีการดำเนินการเพิ่มเติมเกี่ยวกับมูลค่าเนื้อหา โดยเฉพาะsnapToและanimateDecaysnapToจะตั้งค่าปัจจุบันเป็นค่าเป้าหมายทันที ซึ่งจะเป็นประโยชน์เมื่อภาพเคลื่อนไหวไม่ใช่แหล่งข้อมูลที่ถูกต้องเพียงแหล่งเดียว และต้อง ซิงค์กับสถานะอื่นๆ เช่น เหตุการณ์การแตะanimateDecayเริ่มภาพเคลื่อนไหวที่ช้าลงจากความเร็วที่ระบุ ซึ่งมีประโยชน์ในการใช้ลักษณะการทำงานแบบดีด
ดูข้อมูลเพิ่มเติมได้ที่ท่าทางสัมผัสและภาพเคลื่อนไหว
โดยค่าเริ่มต้น Animatable รองรับ Float และ Color แต่คุณสามารถใช้ประเภทข้อมูลใดก็ได้โดยระบุ TwoWayConverter ดูข้อมูลเพิ่มเติมได้ที่ AnimationVector
คุณปรับแต่งข้อกำหนดของภาพเคลื่อนไหวได้โดยระบุ AnimationSpec
ดูข้อมูลเพิ่มเติมได้ที่ AnimationSpec
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
DecayAnimation ไม่จำเป็นต้องระบุ
targetValue เหมือนกับ TargetBasedAnimation แต่จะคำนวณ targetValue ตาม
เงื่อนไขเริ่มต้นที่กำหนดโดย initialVelocity และ initialValue รวมถึง DecayAnimationSpec ที่ระบุ
โดยมักใช้ภาพเคลื่อนไหวการสลายตัวหลังจากท่าทางสัมผัสตวัดเพื่อชะลอองค์ประกอบให้หยุด ความเร็วของภาพเคลื่อนไหวจะเริ่มต้นที่ค่าที่ initialVelocityVector ตั้งค่าไว้และจะช้าลงเมื่อเวลาผ่านไป
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ปรับแต่งภาพเคลื่อนไหว
- ภาพเคลื่อนไหวใน Compose
- ตัวแก้ไขและ Composable ของภาพเคลื่อนไหว