เคลื่อนไหวค่าเดี่ยวด้วย 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, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
โปรดทราบว่าคุณไม่จําเป็นต้องสร้างอินสแตนซ์ของคลาสภาพเคลื่อนไหวหรือจัดการการขัดจังหวะ เบื้องหลัง ระบบจะสร้างออบเจ็กต์ภาพเคลื่อนไหว (นั่นคือAnimatable
ตัวอย่าง) และจดจำไว้ที่ตำแหน่งการเรียกใช้ โดยมีค่าเป้าหมายแรกเป็นค่าเริ่มต้น จากนั้นทุกครั้งที่คุณระบุค่าเป้าหมายอื่นให้กับคอมโพสิเบิลนี้ ระบบจะเริ่มภาพเคลื่อนไหวไปยังค่านั้นโดยอัตโนมัติ หากมีภาพเคลื่อนไหวที่แสดงอยู่อยู่แล้ว ภาพเคลื่อนไหวจะเริ่มต้นจากค่าปัจจุบัน (และความเร็ว) และเคลื่อนไหวไปยังค่าเป้าหมาย ในระหว่างภาพเคลื่อนไหว 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 = rememberTransition(currentState, label = "box state") // ……
สำหรับทรานซิชันที่ซับซ้อนมากขึ้นซึ่งเกี่ยวข้องกับฟังก์ชันคอมโพสิเบิลหลายรายการ คุณสามารถใช้ createChildTransition
เพื่อสร้างทรานซิชันย่อยได้ เทคนิคนี้มีประโยชน์ในการแยกข้อกังวลระหว่างคอมโพเนนต์ย่อยหลายรายการในคอมโพสิเบิลที่ซับซ้อน ทรานซิชันหลักจะรับรู้ค่าภาพเคลื่อนไหวทั้งหมดในทรานซิชันย่อย
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
มีการเปลี่ยนแปลง ฟังก์ชันส่วนขยายเหล่านี้ช่วยให้สามารถยกระดับภาพเคลื่อนไหว "เข้า/ออก/เปลี่ยนขนาด" ทั้งหมดซึ่งควรจะอยู่ใน 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") } } } }
บรรจุการเปลี่ยนหน้าสไลด์และทำให้นํากลับมาใช้ใหม่ได้
สําหรับกรณีการใช้งานแบบง่าย การกําหนดภาพเคลื่อนไหวของทรานซิชันในคอมโพสิเบิลเดียวกับ 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 ที่ใช้ coroutine เพื่อสร้างภาพเคลื่อนไหวของค่าเดี่ยว updateTransition
สร้างออบเจ็กต์การเปลี่ยนรูปแบบที่จัดการค่าที่มีภาพเคลื่อนไหวหลายค่าและเรียกใช้ค่าเหล่านั้นตามการเปลี่ยนแปลงสถานะ rememberInfiniteTransition
คล้ายกับ แต่สร้างการเปลี่ยนแบบไม่สิ้นสุดซึ่งจัดการภาพเคลื่อนไหวหลายรายการที่ทำงานไปเรื่อยๆ ได้ API ทั้งหมดเหล่านี้เป็นองค์ประกอบที่คอมโพสได้ ยกเว้น Animatable
ซึ่งหมายความว่าคุณจะสร้างภาพเคลื่อนไหวเหล่านี้นอกการคอมโพสได้
API ทั้งหมดเหล่านี้อิงตาม Animation
API พื้นฐานมากกว่า แม้ว่าแอปส่วนใหญ่จะไม่โต้ตอบกับ Animation
โดยตรง แต่ความสามารถในการปรับแต่งบางอย่างของ Animation
จะพร้อมใช้งานผ่าน API ระดับสูงขึ้น ดูข้อมูลเพิ่มเติมเกี่ยวกับ AnimationVector
และ AnimationSpec
ได้ที่หัวข้อปรับแต่งภาพเคลื่อนไหว
Animatable
: ภาพเคลื่อนไหวค่าเดี่ยวที่อิงตาม Coroutine
Animatable
คือตัวเก็บค่าที่สามารถสร้างภาพเคลื่อนไหวของค่าขณะที่เปลี่ยนแปลงผ่าน animateTo
นี่คือ API ที่รองรับการใช้งาน animate*AsState
ซึ่งช่วยให้มั่นใจได้ว่าการเปลี่ยนแปลงจะต่อเนื่องและแยกกันอยู่เสมอ ซึ่งหมายความว่าการเปลี่ยนแปลงค่าจะต่อเนื่องเสมอและภาพเคลื่อนไหวที่ดำเนินอยู่จะถูกยกเลิก
ฟีเจอร์หลายอย่างของ Animatable
รวมถึง animateTo
มีให้ใช้งานเป็นฟังก์ชันที่ระงับ ซึ่งหมายความว่าต้องรวมไว้ในขอบเขต coroutine ที่เหมาะสม เช่น คุณสามารถใช้คอมโพสิเบิล 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
โดยขึ้นอยู่กับค่าของ Flag บูลีน ok
การเปลี่ยนแปลงค่าบูลีนหลังจากนั้นจะเริ่มภาพเคลื่อนไหวเป็นสีอื่น หากมีภาพเคลื่อนไหวที่ดำเนินอยู่เมื่อค่ามีการเปลี่ยนแปลง ระบบจะยกเลิกภาพเคลื่อนไหวนั้นและภาพเคลื่อนไหวใหม่จะเริ่มจากค่าสแนปชอตปัจจุบันที่มีความเร็วปัจจุบัน
นี่คือการใช้งานภาพเคลื่อนไหวที่สำรองข้อมูล animate*AsState
API
ที่กล่าวถึงในส่วนก่อนหน้า เมื่อเทียบกับ animate*AsState
การใช้ Animatable
โดยตรงช่วยให้เราควบคุมได้ละเอียดยิ่งขึ้นในหลายด้าน ประการแรก Animatable
มีค่าเริ่มต้นที่แตกต่างจากค่าเป้าหมายแรกได้
ตัวอย่างเช่น ตัวอย่างโค้ดด้านบนแสดงกล่องสีเทาในตอนแรก ซึ่งจะเริ่มเคลื่อนไหวเป็นสีเขียวหรือสีแดงทันที ประการที่ 2 คือ 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 ปิดอยู่
- ปรับแต่งภาพเคลื่อนไหว {:#customize-animations}
- ภาพเคลื่อนไหวใน Compose
- ตัวปรับเปลี่ยนภาพเคลื่อนไหวและคอมโพสิเบิล