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

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

Composables ที่เคลื่อนไหวได้ในตัว

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

Green 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

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 Composable กับภาพเคลื่อนไหวเข้าและ ออกขององค์ประกอบย่อยเอง

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 จะไม่สามารถพิจารณาภาพเคลื่อนไหวเหล่านั้นได้ และอาจนำเนื้อหาที่สามารถจัดองค์ประกอบออกก่อนที่ภาพเคลื่อนไหวจะสิ้นสุด

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 แบบ Infix คุณสามารถใช้ 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 กับบุตรหลานแต่ละคนทั้งโดยตรงและโดยอ้อม แยกกัน

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

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

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

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

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

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

Green composable animating its size change smoothly.
รูปที่ 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