ภาพเคลื่อนไหวตามมูลค่า

ทำให้ค่าเดียวเคลื่อนไหวด้วย 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

แผนภาพแสดงความสัมพันธ์ระหว่าง API ของภาพเคลื่อนไหวระดับต่ำต่างๆ

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 ตั้งไว้ และช้าลงเมื่อเวลาผ่านไป