หน้านี้อธิบายวิธีสร้างภาพเคลื่อนไหวตามค่าใน 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 จัดการภาพเคลื่อนไหวอย่างน้อย 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 เพื่อดำเนินการนี้ได้ เช่น ช่วยให้คุณเริ่มภาพเคลื่อนไหวได้ทันทีที่โค้ดเข้าสู่ Composition
// 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 จากภายนอกได้ แทนที่จะเป็นพารามิเตอร์บูลีน 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") } } } }
รวมการเปลี่ยนผ่านและทำให้ใช้ซ้ำได้
สำหรับกรณีการใช้งานที่ตรงไปตรงมา การกำหนดภาพเคลื่อนไหวการเปลี่ยนภาพใน 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 แต่ภาพเคลื่อนไหวจะเริ่มทำงานทันทีที่เข้าสู่ Composition และจะไม่หยุดจนกว่าจะถูกนำออก คุณสามารถสร้างอินสแตนซ์ของ InfiniteTransition ด้วย rememberInfiniteTransition และเพิ่มภาพเคลื่อนไหวย่อยด้วย animateColor, animateFloat หรือ animateValue นอกจากนี้ คุณยังต้องระบุ 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 ทั้งหมดนี้เป็น Composable ยกเว้น Animatable ซึ่งหมายความว่าคุณสามารถสร้างภาพเคลื่อนไหวเหล่านี้ภายนอก Composition ได้
API ทั้งหมดนี้อิงตาม API Animation พื้นฐานเพิ่มเติม แม้ว่าแอปส่วนใหญ่จะไม่โต้ตอบกับ Animation โดยตรง แต่คุณสามารถเข้าถึงความสามารถในการปรับแต่งบางอย่างผ่าน API ระดับสูงได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับ AnimationVector และ AnimationSpec ได้ที่ปรับแต่งภาพเคลื่อนไหว
Animatable: ภาพเคลื่อนไหวค่าเดียวที่อิงตามโครูทีน
Animatable คือตัวยึดค่าที่สามารถทำให้ค่าเคลื่อนไหวได้เมื่อมีการเปลี่ยนแปลงโดยใช้ animateTo นี่คือ API ที่สนับสนุนการใช้งาน animate*AsState ซึ่งช่วยให้มั่นใจได้ว่าการทำงานจะดำเนินต่อไปอย่างสอดคล้องกันและไม่เกิดความขัดแย้งกัน ซึ่งหมายความว่าการเปลี่ยนแปลงค่าจะดำเนินไปอย่างต่อเนื่องเสมอ และ Compose จะยกเลิกภาพเคลื่อนไหวที่กำลังดำเนินอยู่
ฟีเจอร์หลายอย่างของ Animatable รวมถึง animateTo เป็นฟังก์ชันระงับ
ซึ่งหมายความว่าคุณต้องรวมไว้ในขอบเขต Coroutine ที่เหมาะสม เช่น คุณสามารถใช้ Composable 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 สีจะเคลื่อนไหวเป็น Color.Green หรือ Color.Red โดยขึ้นอยู่กับค่าของแฟล็กบูลีน ok การเปลี่ยนแปลงค่าบูลีนในภายหลังจะเริ่มภาพเคลื่อนไหวเป็นสีอื่น
หากภาพเคลื่อนไหวกำลังทำงานอยู่เมื่อค่าเปลี่ยนแปลง Compose จะยกเลิกภาพเคลื่อนไหวนั้น และภาพเคลื่อนไหวใหม่จะเริ่มจากค่าสแนปชอตปัจจุบันด้วยความเร็วปัจจุบัน
API Animatable นี้เป็นการใช้งานเบื้องหลังสำหรับ animate*AsState ที่กล่าวถึงในส่วนก่อนหน้า การใช้ Animatable โดยตรงจะช่วยให้ควบคุมได้ละเอียดยิ่งขึ้นในหลายๆ ด้าน ดังนี้
- ประการแรก
Animatableอาจมีค่าเริ่มต้นที่แตกต่างจากค่าเป้าหมายแรก ตัวอย่างเช่น ตัวอย่างโค้ดก่อนหน้าแสดงกล่องสีเทาในตอนแรก ซึ่งจะเคลื่อนไหวเป็นสีเขียวหรือสีแดงทันที - ประการที่ 2
Animatableมีการดำเนินการเพิ่มเติมเกี่ยวกับค่าเนื้อหา โดยเฉพาะsnapToและanimateDecaysnapToจะตั้งค่าปัจจุบันเป็นค่าเป้าหมายทันที ซึ่งจะเป็นประโยชน์เมื่อภาพเคลื่อนไหวไม่ใช่แหล่งข้อมูลที่ถูกต้องเพียงแหล่งเดียวและต้องซิงค์กับสถานะอื่นๆ เช่น การโต้ตอบแบบสัมผัสanimateDecayเริ่มภาพเคลื่อนไหวที่ช้าลงจากความเร็วที่กำหนด ซึ่งมีประโยชน์สำหรับการใช้ลักษณะการทำงานแบบเหวี่ยง
ดูข้อมูลเพิ่มเติมได้ที่ท่าทางสัมผัสและภาพเคลื่อนไหว
โดยค่าเริ่มต้น Animatable จะรองรับ Float และ Color แต่คุณสามารถใช้ข้อมูลประเภทใดก็ได้โดยระบุ TwoWayConverter ดูข้อมูลเพิ่มเติมได้ที่ AnimationVector
คุณปรับแต่งข้อกำหนดของภาพเคลื่อนไหวได้โดยระบุ AnimationSpec
ดูข้อมูลเพิ่มเติมได้ที่ AnimationSpec
Animation: ภาพเคลื่อนไหวที่ควบคุมด้วยตนเอง
Animation เป็น API ภาพเคลื่อนไหวระดับต่ำสุดที่พร้อมใช้งาน ภาพเคลื่อนไหวหลายๆ ภาพที่เราได้เห็นจนถึงตอนนี้สร้างขึ้นจาก Animation Animation มี 2 ประเภทย่อย ได้แก่ 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
DecayAnimation ไม่จำเป็นต้องระบุ targetValue ซึ่งต่างจาก TargetBasedAnimation แต่จะคำนวณ targetValue โดยอิงตามเงื่อนไขเริ่มต้นที่กำหนดโดย initialVelocity และ initialValue และ DecayAnimationSpec ที่ระบุ
ภาพเคลื่อนไหวแบบลดความเร็วมักใช้หลังจากท่าทางสัมผัสแบบเหวี่ยงเพื่อชะลอองค์ประกอบให้หยุดลง ความเร็วของภาพเคลื่อนไหวจะเริ่มต้นที่ค่าที่ initialVelocityVector กำหนดไว้และช้าลงเมื่อเวลาผ่านไป
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ปรับแต่งภาพเคลื่อนไหว
- ภาพเคลื่อนไหวใน Compose
- ตัวแก้ไขภาพเคลื่อนไหวและ Composable