ทำให้ค่าเดียวเคลื่อนไหวด้วย animate*AsState
ฟังก์ชัน animate*AsState
เป็น API ภาพเคลื่อนไหวที่ง่ายที่สุดใน Compose สำหรับ
การทำให้ค่าเดียวเคลื่อนไหว คุณระบุเฉพาะค่าเป้าหมาย (หรือค่าสิ้นสุด) และ
API เริ่มภาพเคลื่อนไหวจากค่าปัจจุบันไปยังค่าที่ระบุ
ด้านล่างเป็นตัวอย่างของการทำให้อัลฟ่าเคลื่อนไหวโดยใช้ API นี้ เพียงแค่การรวม
ค่าเป้าหมายใน animateFloatAsState
ค่าอัลฟ่ากลายเป็นค่าภาพเคลื่อนไหวแล้ว
ระหว่างค่าที่ระบุ (ในกรณีนี้คือ 1f
หรือ 0.5f
)
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f) Box( Modifier.fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
โปรดทราบว่าคุณไม่จำเป็นต้องสร้างอินสแตนซ์ของคลาสภาพเคลื่อนไหวหรือแฮนเดิลใดๆ
ขัดข้อง ในส่วนฮู้ด จะมีออบเจ็กต์ภาพเคลื่อนไหว (ได้แก่ Animatable
) จะได้รับการสร้างและจดจำไว้ที่ไซต์การโทร โดยมีเป้าหมายแรก
เป็นค่าเริ่มต้น จากนั้น เมื่อใดก็ตามที่คุณใส่ Composable นี้
ค่าเป้าหมายที่ต่างกัน ภาพเคลื่อนไหวจะเริ่มต้นโดยอัตโนมัติเมื่อค่านั้น
หากมีภาพเคลื่อนไหวอยู่แล้วขณะบิน ภาพเคลื่อนไหวจะเริ่มต้นจาก
ค่าปัจจุบัน (และอัตราความเร็ว) และเคลื่อนที่ไปยังค่าเป้าหมาย ในช่วง
ภาพเคลื่อนไหว Composable นี้จะได้รับการปรับแต่งใหม่ และแสดงผลภาพเคลื่อนไหวที่อัปเดต
ทุกเฟรม
เขียนโดยมีฟังก์ชัน 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 = updateTransition(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
และเรียกใช้การเปลี่ยนเข้า/ออกตามที่จำเป็นเมื่อ
targetState
ของ Transition
มีการเปลี่ยนแปลง ฟังก์ชันส่วนขยายเหล่านี้ช่วยให้
ภาพเคลื่อนไหว Enter/exit/sizeTransform ที่จะเป็นภายในสำหรับ
AnimatedVisibility
/AnimatedContent
เพื่อเตรียมเข้าสู่Transition
เมื่อใช้ฟังก์ชันของส่วนขยายเหล่านี้ สถานะของ AnimatedVisibility
/AnimatedContent
สามารถสังเกตเห็นการเปลี่ยนแปลงจากภายนอก แทนที่จะเป็นพารามิเตอร์ visible
บูลีน
AnimatedVisibility
เวอร์ชันนี้ใช้ lambda ที่แปลงระดับบนสุด
เปลี่ยนสถานะเป้าหมายของเป็นบูลีน
ดูรายละเอียดได้ที่ Animated visibility และ 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), elevation = 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
มีภาพเคลื่อนไหวย่อย เช่น Transition
อย่างน้อย 1 รายการ แต่
ภาพเคลื่อนไหวจะเริ่มต้นทันที
เมื่อเข้าสู่องค์ประกอบ และไม่
หยุดจนกว่าจะนำออก คุณสร้างอินสแตนซ์ของ InfiniteTransition
ได้
ด้วย rememberInfiniteTransition
คุณเพิ่มภาพเคลื่อนไหวย่อยได้ด้วย
animateColor
, animatedFloat
หรือ animatedValue
นอกจากนี้ คุณยังต้องระบุ
infinitefiniteable เพื่อระบุภาพเคลื่อนไหว
val infiniteTransition = rememberInfiniteTransition() val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ) ) Box(Modifier.fillMaxSize().background(color))
API ภาพเคลื่อนไหวระดับต่ำ
API ภาพเคลื่อนไหวระดับสูงทั้งหมดที่กล่าวถึงในส่วนก่อนหน้านี้นั้นสร้างขึ้นจาก พื้นฐานของ API ภาพเคลื่อนไหวระดับต่ำ
ฟังก์ชัน animate*AsState
เป็น API ที่ใช้ง่ายที่สุดที่แสดงผลแบบ Instant
ค่าจะเปลี่ยนเป็นค่าของภาพเคลื่อนไหว ได้รับการสนับสนุนโดย Animatable
ซึ่งเป็น
API แบบ coroutine สำหรับการทำให้ค่าเดี่ยวเคลื่อนไหว updateTransition
สร้าง
ออบเจ็กต์การเปลี่ยนที่สามารถจัดการกับค่าภาพเคลื่อนไหวหลายค่าและเรียกใช้ตาม
เมื่อมีการเปลี่ยนสถานะ rememberInfiniteTransition
มีความคล้ายคลึงกันแต่สร้าง
การเปลี่ยนได้ไม่รู้จบที่สามารถจัดการภาพเคลื่อนไหวมากมายที่ยังคงทำงานต่อไป
ไปเรื่อยๆ API เหล่านี้ทั้งหมดเป็น Composable ยกเว้น Animatable
ซึ่ง
นั่นหมายความว่าภาพเคลื่อนไหวเหล่านี้สามารถสร้างขึ้นได้โดยไม่ต้องผ่านการจัดวางองค์ประกอบ
API เหล่านี้ทั้งหมดอิงตาม API Animation
ขั้นพื้นฐาน แม้ว่าส่วนใหญ่
แอปจะไม่โต้ตอบกับ Animation
โดยตรง การกำหนดค่าบางอย่าง
สำหรับ Animation
พร้อมใช้งานผ่าน API ระดับสูงกว่า โปรดดู
ปรับแต่งภาพเคลื่อนไหวเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับ
AnimationVector
และ AnimationSpec
Animatable
: ภาพเคลื่อนไหวแบบค่าเดี่ยวที่อิงตาม Coroutine
Animatable
คือเจ้าของค่าที่สามารถทำให้ค่าเคลื่อนไหวได้เนื่องจากมีการเปลี่ยนแปลงผ่าน
animateTo
นี่คือ API ที่สำรองการติดตั้งใช้งาน animate*AsState
มีความต่อเนื่องและสอดคล้องสม่ำเสมอ ซึ่งหมายความว่า
การเปลี่ยนแปลงค่าจะเป็นไปอย่างต่อเนื่องและภาพเคลื่อนไหวที่ดำเนินอยู่ทั้งหมดจะถูกยกเลิก
ฟีเจอร์หลายรายการของ 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
ขึ้นอยู่กับค่าของแฟล็กบูลีน
ok
สีจะเคลื่อนไหวไปที่ Color.Green
หรือ Color.Red
ลำดับต่อๆ ไป
การเปลี่ยนเป็นค่าบูลีนจะเริ่มภาพเคลื่อนไหวเป็นสีอื่น หากมี
ภาพเคลื่อนไหวต่อเนื่องเมื่อเปลี่ยนค่า ภาพเคลื่อนไหวถูกยกเลิก และ
ภาพเคลื่อนไหวใหม่จะเริ่มต้นจากค่าสแนปชอตปัจจุบันที่มีความเร็วปัจจุบัน
นี่คือการใช้ภาพเคลื่อนไหวที่สำรองข้อมูล API ของ animate*AsState
ที่กล่าวถึงในส่วนก่อนหน้านี้ เมื่อเทียบกับ animate*AsState
การใช้
Animatable
ช่วยให้เราควบคุมได้อย่างละเอียดมากขึ้นในหลายๆ ด้าน อันดับแรก
Animatable
อาจมีค่าเริ่มต้นแตกต่างจากค่าเป้าหมายแรก
ตัวอย่างเช่น ตัวอย่างโค้ดข้างต้นแสดงกล่องสีเทาในตอนแรก
เริ่มเคลื่อนไหวเป็นสีเขียวหรือสีแดง อย่างที่ 2 Animatable
จะระบุมากกว่า
การดำเนินการกับค่าเนื้อหา ซึ่งก็คือ snapTo
และ animateDecay
snapTo
ตั้งค่าปัจจุบันเป็นค่าเป้าหมายทันที วิธีนี้มีประโยชน์เมื่อ
ของภาพเคลื่อนไหวเองไม่ใช่แหล่งที่มา
ที่ถูกต้องเพียงแหล่งเดียว และต้องซิงค์กับ
เช่น กิจกรรมการสัมผัส animateDecay
เริ่มภาพเคลื่อนไหวที่ช้าลง
จากความเร็วที่กำหนด การดำเนินการนี้มีประโยชน์ในการใช้ลักษณะการทำงานของการดึงข้อมูล โปรดดู
ท่าทางสัมผัสและภาพเคลื่อนไหวสำหรับข้อมูลเพิ่มเติม
Animatable
รองรับ Float
และ Color
ในตัว แต่ข้อมูลทุกประเภทสามารถ
ให้ใช้โดยการระบุ TwoWayConverter
โปรดดู
Animation Vector เพื่อดูข้อมูลเพิ่มเติม
คุณปรับแต่งข้อกำหนดเกี่ยวกับภาพเคลื่อนไหวได้โดยใส่ AnimationSpec
ดูข้อมูลเพิ่มเติมได้ที่ AnimationSpec
Animation
: ภาพเคลื่อนไหวที่ควบคุมด้วยตนเอง
Animation
เป็น Animation API ระดับต่ำสุดที่มีให้บริการ ภาพเคลื่อนไหวหลายๆ แบบ
ที่เราได้เห็นแล้วว่าจนถึงตอนนี้ เรายังคงพัฒนา
เรื่องราวของแอนิเมชัน มี 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 { mutableStateOf(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
ตั้งไว้
และช้าลงเมื่อเวลาผ่านไป
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ปรับแต่งภาพเคลื่อนไหว {:#customize-animations}
- ภาพเคลื่อนไหวใน Compose
- ตัวแก้ไขภาพเคลื่อนไหวและ Composable