ตัวแก้ไขภาพเคลื่อนไหวและ Composable

Compose มาพร้อมกับ Composable และตัวแก้ไขในตัวสำหรับจัดการกรณีการใช้งานภาพเคลื่อนไหวทั่วไป

Composable แบบเคลื่อนไหวในตัว

Compose มี Composable หลายรายการที่ทำให้ลักษณะที่ปรากฏของเนื้อหา การหายไป และการเปลี่ยนแปลงเลย์เอาต์เคลื่อนไหวได้

ทำให้การปรากฏและการหายไปเคลื่อนไหว

Composable สีเขียวแสดงและซ่อนตัวเอง
รูปที่ 1 การทำให้การปรากฏและการหายไปของรายการในคอลัมน์เป็นภาพเคลื่อนไหว

Composable AnimatedVisibility จะแสดงภาพเคลื่อนไหวของการปรากฏและการหายไปของเนื้อหา

var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
    // your composable here
    // ...
}

โดยค่าเริ่มต้น เนื้อหาจะปรากฏขึ้นโดยการค่อยๆ ปรากฏและขยาย และจะหายไปโดยการค่อยๆ หายไปและหดตัว ปรับแต่งการเปลี่ยนนี้โดยระบุออบเจ็กต์ EnterTransition และ ExitTransition

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text(
        "Hello",
        Modifier
            .fillMaxWidth()
            .height(200.dp)
    )
}

ดังที่แสดงในตัวอย่างก่อนหน้า คุณสามารถรวมออบเจ็กต์ EnterTransition หรือ ExitTransition หลายรายการเข้ากับโอเปอเรเตอร์ + และแต่ละรายการจะยอมรับพารามิเตอร์ที่ไม่บังคับเพื่อปรับแต่งลักษณะการทำงานของออบเจ็กต์ ดูข้อมูลเพิ่มเติมได้ที่หน้าข้อมูลอ้างอิง

ตัวอย่างการเปลี่ยนเข้าและออก

EnterTransition ExitTransition
fadeIn
องค์ประกอบ UI ค่อยๆ ปรากฏขึ้นในมุมมอง
fadeOut
องค์ประกอบ UI ค่อยๆ จางหายไปจากมุมมอง
slideIn
องค์ประกอบ UI เลื่อนเข้ามาในมุมมองจากนอกหน้าจอ
slideOut
องค์ประกอบ UI เลื่อนออกจากมุมมองนอกหน้าจอ
slideInHorizontally
องค์ประกอบ UI จะเลื่อนเข้ามาในมุมมองในแนวนอน
slideOutHorizontally
องค์ประกอบ UI เลื่อนออกไปจากมุมมองในแนวนอน
slideInVertically
องค์ประกอบ UI เลื่อนขึ้นในแนวตั้งเข้ามาในมุมมอง
slideOutVertically
องค์ประกอบ UI เลื่อนออกไปจากมุมมองในแนวตั้ง
scaleIn
องค์ประกอบ UI ขยายขนาดและปรากฏขึ้นในมุมมอง
scaleOut
องค์ประกอบ UI จะลดขนาดลงและหายไปจากมุมมอง
expandIn
องค์ประกอบ UI ขยายเป็นมุมมองจากจุดศูนย์กลาง
shrinkOut
องค์ประกอบ UI จะหดตัวลงจนหายไปจากมุมมองที่จุดศูนย์กลาง
expandHorizontally
องค์ประกอบ UI ขยายในแนวนอนให้เห็นได้
shrinkHorizontally
องค์ประกอบ UI จะหดตัวในแนวนอนจนพ้นมุมมอง
expandVertically
องค์ประกอบ UI จะขยายในแนวตั้งเข้ามาในมุมมอง
shrinkVertically
องค์ประกอบ UI หดตัวในแนวตั้งจนหายไปจากมุมมอง

นอกจากนี้ AnimatedVisibility ยังมีตัวแปรที่รับอาร์กิวเมนต์ MutableTransitionState ด้วย ซึ่งจะช่วยให้คุณเรียกใช้ภาพเคลื่อนไหวได้ทันทีที่เพิ่ม AnimatedVisibility ที่ประกอบกันได้ลงในแผนผังองค์ประกอบ นอกจากนี้ยังเป็นประโยชน์ในการสังเกตสถานะภาพเคลื่อนไหวด้วย

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

ทำให้การเข้าและออกของบุตรหลานเคลื่อนไหว

เนื้อหาภายใน AnimatedVisibility (องค์ประกอบย่อยโดยตรงหรือโดยอ้อม) สามารถใช้ตัวแก้ไข animateEnterExit เพื่อระบุลักษณะการทำงานของภาพเคลื่อนไหวที่แตกต่างกันสำหรับแต่ละองค์ประกอบได้ เอฟเฟกต์ภาพสำหรับคอมโพสิเบิลย่อยแต่ละรายการเหล่านี้เป็นการผสมผสานระหว่างภาพเคลื่อนไหวที่ระบุไว้ที่คอมโพสิเบิล AnimatedVisibility และภาพเคลื่อนไหวเข้าและออกของคอมโพสิเบิลย่อยเอง

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

ในบางกรณี คุณอาจต้องการให้ AnimatedVisibility ไม่ใช้ภาพเคลื่อนไหวเลย เพื่อให้เด็กๆ แต่ละคนมีภาพเคลื่อนไหวที่แตกต่างกันตามanimateEnterExit หากต้องการดำเนินการดังกล่าว ให้ระบุ EnterTransition.None และ ExitTransition.None ที่ Composable AnimatedVisibility

เพิ่มภาพเคลื่อนไหวที่กำหนดเอง

หากต้องการเพิ่มเอฟเฟกต์ภาพเคลื่อนไหวที่กำหนดเองนอกเหนือจากภาพเคลื่อนไหวการเข้าและออกในตัว ให้เข้าถึงอินสแตนซ์ Transition ที่สำคัญโดยใช้พร็อพเพอร์ตี้ transition ภายใน Lambda ของเนื้อหาสำหรับ AnimatedVisibility สถานะภาพเคลื่อนไหวที่เพิ่มลงในอินสแตนซ์การเปลี่ยนภาพจะทำงานพร้อมกันกับภาพเคลื่อนไหวเข้าและออกของ AnimatedVisibility AnimatedVisibility จะรอจนกว่าภาพเคลื่อนไหวทั้งหมดใน Transition จะเสร็จสิ้นก่อนที่จะนำเนื้อหาออก สำหรับภาพเคลื่อนไหวออกที่สร้างขึ้นโดยไม่ขึ้นอยู่กับ Transition (เช่น การใช้ animate*AsState) AnimatedVisibility จะไม่สามารถพิจารณาภาพเคลื่อนไหวดังกล่าวได้ จึงอาจนำ Composable ของเนื้อหาออกก่อนที่ภาพเคลื่อนไหวจะเสร็จสิ้น

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(background)
    )
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Transition เพื่อจัดการภาพเคลื่อนไหวได้ที่ทำให้พร็อพเพอร์ตี้หลายรายการเคลื่อนไหวพร้อมกันด้วยการเปลี่ยนภาพ

ภาพเคลื่อนไหวตามสถานะเป้าหมาย

AnimatedContent Composable จะทำให้เนื้อหาเคลื่อนไหวเมื่อมีการเปลี่ยนแปลงตามสถานะเป้าหมาย

Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

โดยค่าเริ่มต้น เนื้อหาเริ่มต้นจะค่อยๆ จางหายไป จากนั้นเนื้อหาเป้าหมายจะค่อยๆ ปรากฏขึ้น (ลักษณะการทำงานนี้เรียกว่าการจางหาย) คุณปรับแต่งลักษณะการทำงานของภาพเคลื่อนไหวนี้ได้โดยระบุออบเจ็กต์ ContentTransform ให้กับพารามิเตอร์ transitionSpec คุณสามารถสร้างอินสแตนซ์ของ ContentTransform ได้โดยการรวมออบเจ็กต์ EnterTransition เข้ากับออบเจ็กต์ ExitTransition โดยใช้ฟังก์ชัน infix with คุณสามารถใช้ SizeTransform กับออบเจ็กต์ ContentTransform โดยแนบกับฟังก์ชัน infix using

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() togetherWith
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition กำหนดลักษณะที่เนื้อหาเป้าหมายควรปรากฏ และ ExitTransition กำหนดลักษณะที่เนื้อหาเริ่มต้นควรหายไป นอกจากฟังก์ชัน EnterTransition และ ExitTransition ทั้งหมดที่มีให้สำหรับ AnimatedVisibility แล้ว AnimatedContent ยังมี slideIntoContainer และ slideOutOfContainer ด้วย ซึ่งเป็นทางเลือกที่สะดวกแทน slideInHorizontally/Vertically และ slideOutHorizontally/Vertically ที่คำนวณระยะทางสไลด์ตามขนาดของเนื้อหาเริ่มต้นและเนื้อหาเป้าหมายของเนื้อหา AnimatedContent

SizeTransform กำหนดวิธีที่ขนาดควรเคลื่อนไหวระหว่างเนื้อหาเริ่มต้นและเนื้อหาเป้าหมาย คุณจะเข้าถึงได้ทั้งขนาดเริ่มต้นและขนาดเป้าหมายเมื่อสร้างภาพเคลื่อนไหว นอกจากนี้ SizeTransform ยังควบคุมว่าจะตัดเนื้อหาให้พอดีกับขนาดคอมโพเนนต์ระหว่างภาพเคลื่อนไหวหรือไม่

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) togetherWith
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }, label = "size transform"
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

ทำให้การเปลี่ยนภาพเข้าและออกขององค์ประกอบย่อยเคลื่อนไหว

ตัวแก้ไข animateEnterExit จะพร้อมใช้งานภายใน Lambda ของเนื้อหาของ AnimatedContent เช่นเดียวกับ AnimatedVisibility ใช้ตัวเลือกนี้เพื่อใช้ EnterAnimation และ ExitAnimation กับหน่วยย่อยโดยตรงหรือโดยอ้อมแต่ละหน่วยแยกกัน

เพิ่มภาพเคลื่อนไหวที่กำหนดเอง

ฟิลด์ transition จะพร้อมใช้งานภายใน Lambda เนื้อหาของ AnimatedContent เช่นเดียวกับ AnimatedVisibility ใช้ตัวเลือกนี้เพื่อสร้างเอฟเฟกต์ภาพเคลื่อนไหวที่กำหนดเองซึ่งทำงานพร้อมกันกับการเปลี่ยน AnimatedContent ดูรายละเอียดได้ที่ updateTransition

สร้างภาพเคลื่อนไหวระหว่าง 2 เลย์เอาต์

Crossfade จะเคลื่อนไหวระหว่าง 2 เลย์เอาต์ด้วยภาพเคลื่อนไหวแบบครอสเฟด เมื่อสลับค่าที่ส่งไปยังพารามิเตอร์ current เนื้อหาจะเปลี่ยนด้วยภาพเคลื่อนไหวแบบครอสเฟด

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

ตัวแก้ไขภาพเคลื่อนไหวในตัว

Compose มีตัวแก้ไขสำหรับการสร้างภาพเคลื่อนไหวของการเปลี่ยนแปลงที่เฉพาะเจาะจงใน Composable โดยตรง

ทำให้การเปลี่ยนแปลงขนาด Composable เป็นภาพเคลื่อนไหว

คอมโพสิเบิลสีเขียวที่เคลื่อนไหวเพื่อเปลี่ยนขนาดอย่างราบรื่น
รูปที่ 2 Composable ที่เคลื่อนไหวอย่างราบรื่นระหว่างขนาดเล็กและขนาดใหญ่

ตัวแก้ไข animateContentSize จะทำให้การเปลี่ยนขนาดเคลื่อนไหว

var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

ภาพเคลื่อนไหวของรายการในลิสต์

หากต้องการทำให้การจัดเรียงรายการใหม่ภายใน Lazy list หรือ Lazy grid เคลื่อนไหวได้ โปรดดูเอกสารประกอบเกี่ยวกับภาพเคลื่อนไหวของรายการเลย์เอาต์แบบ Lazy