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

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

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

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

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

คอมโพสิเบิล 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

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

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 ที่คอมโพสิเบิล AnimatedVisibility

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

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

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 ได้ที่ updateTransition

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

คอมโพสิเบิล AnimatedContent จะแสดงภาพเคลื่อนไหวของเนื้อหาเมื่อเนื้อหาเปลี่ยนแปลงตามสถานะเป้าหมาย

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")
    }
}

โปรดทราบว่าคุณควรใช้พารามิเตอร์ lambda และแสดงผลในเนื้อหาเสมอ API ใช้ค่านี้เป็นคีย์ในการระบุเนื้อหาที่แสดงอยู่ในปัจจุบัน

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

สร้างภาพเคลื่อนไหวสำหรับทรานซิชันของเด็กที่เข้ามาและออก

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

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

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

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

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

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

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

สร้างภาพเคลื่อนไหวการเปลี่ยนแปลงขนาดขององค์ประกอบที่คอมโพสิเบิลด้วย animateContentSize

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

ตัวแก้ไข 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 หรือตารางกริด โปรดดูเอกสารประกอบเกี่ยวกับภาพเคลื่อนไหวของรายการเลย์เอาต์แบบ Lazy