Công cụ sửa đổi ảnh động và thành phần kết hợp

Compose đi kèm với các thành phần kết hợp và đối tượng sửa đổi được tích hợp sẵn để xử lý các trường hợp sử dụng ảnh động phổ biến.

Thành phần kết hợp ảnh động tích hợp sẵn

Tạo ảnh động xuất hiện và biến mất bằng AnimatedVisibility

Thành phần kết hợp màu xanh lục tự hiển thị và ẩn
Hình 1. Tạo ảnh động cho sự xuất hiện và biến mất của một mục trong cột

Thành phần kết hợp AnimatedVisibility tạo ảnh động làm xuất hiện nội dung của ảnh động và làm nội dung đó biến mất.

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
    // ...
}

Theo mặc định, nội dung xuất hiện theo kiểu rõ dần và mở rộng, và biến mất theo kiểu mờ dần và thu nhỏ. Quá trình chuyển đổi có thể được tuỳ chỉnh bởi hàm cụ thể EnterTransitionExitTransition.

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

Như có thể thấy trong ví dụ trên, bạn có thể kết hợp nhiều đối tượng EnterTransition hoặc ExitTransition với toán tử + và mỗi đối tượng sẽ chấp nhận các tham số tuỳ chọn để tuỳ chỉnh hoạt động của đối tượng đó. Hãy xem phần tham khảo để biết thêm thông tin.

Ví dụ về EnterTransitionExitTransition

EnterTransition ExitTransition
fadeIn
ảnh động với hiệu ứng rõ dần
fadeOut
ảnh động với hiệu ứng mờ dần
slideIn
ảnh động với hiệu ứng trượt vào
slideOut
ảnh động với hiệu ứng trượt ra
slideInHorizontally
ảnh động với hiệu ứng trượt vào theo chiều ngang
slideOutHorizontally
ảnh động với hiệu ứng trượt ra theo chiều ngang
slideInVertically
ảnh động với hiệu ứng trượt vào theo chiều dọc
slideOutVertically
ảnh động với hiệu ứng trượt ra theo chiều dọc
scaleIn
ảnh động với hiệu ứng thu nhỏ
scaleOut
ảnh động với hiệu ứng phóng to
expandIn
ảnh động với hiệu ứng mở rộng
shrinkOut
ảnh động với hiệu ứng thu nhỏ
expandHorizontally
mở rộng ảnh động theo chiều ngang
shrinkHorizontally
thu nhỏ ảnh động theo chiều ngang
expandVertically
mở rộng ảnh động theo chiều dọc
shrinkVertically
thu nhỏ ảnh động theo chiều dọc

AnimatedVisibility cũng cung cấp một biến thể nhận MutableTransitionState. Biến thể này cho phép bạn kích hoạt một ảnh động ngay khi bạn thêm AnimatedVisibility vào cây sáng tác. Việc này cũng hữu ích khi quan sát trạng thái ảnh động.

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

Nhập và thoát hình ảnh động cho bố cục con

Nội dung trong AnimatedVisibility (bố cục con trực tiếp hoặc gián tiếp) có thể sử dụng công cụ sửa đổi animateEnterExit để xác định chế độ ảnh động khác nhau cho từng thành phần. Hiệu ứng hình ảnh cho mỗi bố cục con là sự kết hợp của các ảnh động được chỉ định trong thành phần kết hợp AnimatedVisibility và nhập và thoát ảnh động của riêng bố cục con.

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…
        }
    }
}

Trong một số trường hợp, bạn có thể không áp dụng AnimatedVisibility cho ảnh động nào để bố cục con có thể có ảnh động của riêng mình bằng animateEnterExit. Để đạt được mục đích này, hãy chỉ định EnterTransition.NoneExitTransition.None tại thành phần kết hợp AnimatedVisibility.

Thêm ảnh động tuỳ chỉnh

Nếu bạn muốn thêm các hiệu ứng ảnh động tuỳ chỉnh ngoài các ảnh động nhập và thoát được tích hợp sẵn, hãy truy cập phiên bản Transition dưới đây thông qua thuộc tính transition bên trong hàm lambda nội dung dành cho AnimatedVisibility. Mọi trạng thái ảnh động được thêm vào phiên bản Chuyển đổi sẽ chạy đồng thời với các ảnh động nhập và thoát của AnimatedVisibility. AnimatedVisibility đợi cho đến khi tất cả các ảnh động trong Transition hoàn tất trước khi xoá nội dung. Đối với các ảnh động thoát được tạo độc lập với Transition (chẳng hạn như sử dụng animate*AsState), AnimatedVisibility sẽ không thể tính đến các ảnh động đó và vì thế, có thể xoá nội dung thành phần kết hợp trước khi hoàn tất.

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

Hãy xem updateTransition để biết thông tin chi tiết về Transition.

Tạo ảnh động dựa trên trạng thái mục tiêu bằng AnimatedContent

Thành phần kết hợp AnimatedContent tạo hoạt ảnh cho nội dung của nó khi thay đổi theo trạng thái mục tiêu.

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

Lưu ý phải luôn sử dụng tham số hàm lambda và phản ánh tham số đó vào nội dung. API sử dụng giá trị này làm chìa khoá để xác định nội dung hiện đang hiển thị.

Theo mặc định, nội dung ban đầu mờ dần và sau đó nội dung mục tiêu rõ dần (hoạt động này được gọi là mờ dần qua). Bạn có thể tuỳ chỉnh hành vi ảnh động này bằng cách chỉ định đối tượng ContentTransform cho tham số transitionSpec. Bạn có thể tạo ContentTransform bằng cách kết hợp EnterTransition với ExitTransition bằng cách sử dụng hàm infix with. Bạn có thể áp dụng SizeTransform cho ContentTransform bằng cách gắn nó với hàm 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 xác định cách hiển thị của nội dung mục tiêu, còn ExitTransition xác định cách làm biến mất nội dung ban đầu. Ngoài tất cả các hàm EnterTransitionExitTransition có sẵn cho AnimatedVisibility, AnimatedContent cung cấp slideIntoContainerslideOutOfContainer. Đây là các lựa chọn thay thế thuận tiện cho slideInHorizontally/VerticallyslideOutHorizontally/Vertically để tính khoảng cách trên trang trình bày dựa trên kích thước của nội dung ban đầu và nội dung mục tiêu của AnimatedContent.

SizeTransform xác định cách kích thước sẽ tạo ảnh động giữa nội dung ban đầu và nội dung mục tiêu. Bạn có quyền truy cập vào cả kích thước ban đầu và kích thước mục tiêu khi tạo ảnh động. SizeTransform cũng kiểm soát việc có nên cắt nội dung thành kích thước thành phần trong các hình ảnh động hay không.

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

Tạo hiệu ứng động cho hiệu ứng chuyển đổi vào và thoát của thành phần con

Cũng giống như AnimatedVisibility, công cụ sửa đổi animateEnterExit có sẵn bên trong nội dung hàm lambda của AnimatedContent. Sử dụng thuộc tính này để áp dụng EnterAnimationExitAnimation cho từng bố cục con trực tiếp hoặc gián tiếp.

Thêm ảnh động tuỳ chỉnh

Giống như AnimatedVisibility, trường transition có sẵn bên trong hàm lambda nội dung của AnimatedContent. Sử dụng trường này để tạo hiệu ứng ảnh động tuỳ chỉnh chạy đồng thời với quá trình chuyển đổi AnimatedContent. Hãy xem updateTransition để biết thông tin chi tiết.

Tạo ảnh động giữa 2 bố cục bằng Crossfade

Crossfade tạo ảnh động giữa hai bố cục bằng một ảnh động chuyển đổi. Bằng cách chuyển đổi, giá trị được chuyển sang thông số current, nội dung được chuyển đổi với một ảnh động chuyển đổi.

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

Đối tượng sửa đổi ảnh động tích hợp

Tạo ảnh động cho các thay đổi về kích thước thành phần kết hợp bằng animateContentSize

Thành phần kết hợp màu xanh lục giúp tạo ảnh động thay đổi kích thước một cách mượt mà.
Hình 2. Thành phần kết hợp tạo ảnh động mượt mà giữa kích thước nhỏ và kích thước lớn hơn

Công cụ sửa đổi animateContentSize sẽ tạo ảnh động cho việc thay đổi về kích thước.

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
        }

) {
}

Ảnh động cho mục danh sách

Nếu bạn đang tìm cách tạo ảnh động cho việc sắp xếp lại thứ tự mục bên trong lưới hoặc danh sách Lazy, hãy xem tài liệu ảnh động của mục bố cục lazy.