หน้านี้อธิบายวิธีสร้างภาพเคลื่อนไหวตามค่าใน 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 นี้ ระบบจะเริ่มภาพเคลื่อนไหวไปยังค่าดังกล่าวโดยอัตโนมัติ หากมีภาพเคลื่อนไหวที่กำลังทำงานอยู่ ภาพเคลื่อนไหวจะเริ่มจาก
ค่าปัจจุบัน (และความเร็ว) และเคลื่อนไหวไปยังค่าเป้าหมาย ในระหว่าง
ภาพเคลื่อนไหว ระบบจะทำการประกอบฟังก์ชันที่ประกอบกันได้นี้อีกครั้งและแสดงค่าภาพเคลื่อนไหวที่อัปเดต
ทุกเฟรม
โดยค่าเริ่มต้น 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") // ……
สำหรับการเปลี่ยนที่ซับซ้อนมากขึ้นซึ่งเกี่ยวข้องกับฟังก์ชัน 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
และทริกเกอร์
ภาพเคลื่อนไหวเข้า ออก และ sizeTransform
ตามต้องการเมื่อ Transition
targetState
เปลี่ยนแปลง ฟังก์ชันส่วนขยายเหล่านี้ช่วยให้คุณยกเลิกการเข้า ออก
และ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 ที่ประกอบได้
คุณทำได้โดยการสร้างคลาสที่เก็บค่าภาพเคลื่อนไหวทั้งหมดและ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
และ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
DecayAnimation
ไม่จำเป็นต้องระบุ
targetValue
เหมือนกับ TargetBasedAnimation
แต่จะคำนวณ targetValue
ตาม
เงื่อนไขเริ่มต้นที่กำหนดโดย initialVelocity
และ initialValue
รวมถึง DecayAnimationSpec
ที่ระบุ
โดยมักใช้ภาพเคลื่อนไหวแบบลดทอนหลังจากท่าทางปัดเพื่อชะลอองค์ประกอบให้หยุด ความเร็วของภาพเคลื่อนไหวจะเริ่มต้นที่ค่าที่ initialVelocityVector
กำหนดและจะช้าลงเมื่อเวลาผ่านไป
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ปรับแต่งภาพเคลื่อนไหว
- ภาพเคลื่อนไหวใน Compose
- ตัวแก้ไขและ Composable ของภาพเคลื่อนไหว