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 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 động tích hợp

Compose cung cấp một số thành phần kết hợp tạo ảnh động cho sự xuất hiện, biến mất và thay đổi bố cục của nội dung.

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

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 một mục xuất hiện và biến mất trong một 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ỏ. Tuỳ chỉnh hiệu ứng chuyển đổi này bằng cách chỉ định các đối tượng 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ư minh hoạ trong ví dụ trước, 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ố không bắt buộc để tuỳ chỉnh hành vi của đối tượng đó. Hãy xem các trang tham khảo để biết thêm thông tin.

Ví dụ về hiệu ứng chuyển đổi xuất hiện và biến mất

EnterTransition ExitTransition
fadeIn
Một phần tử trên giao diện người dùng dần xuất hiện.
fadeOut
Một phần tử trên giao diện người dùng mờ dần khỏi khung hiển thị.
slideIn
Một phần tử trên giao diện người dùng trượt vào khung hiển thị từ ngoài màn hình.
slideOut
Một thành phần trên giao diện người dùng trượt ra khỏi khung hiển thị trên màn hình.
slideInHorizontally
Một phần tử trên giao diện người dùng trượt theo chiều ngang vào khung hiển thị.
slideOutHorizontally
Một thành phần trên giao diện người dùng trượt theo chiều ngang ra khỏi khung hiển thị.
slideInVertically
Một phần tử trên giao diện người dùng trượt theo chiều dọc vào khung hiển thị.
slideOutVertically
Một phần tử trên giao diện người dùng trượt theo chiều dọc ra khỏi khung hiển thị.
scaleIn
Một phần tử trên giao diện người dùng sẽ tăng kích thước và xuất hiện trong khung hiển thị.
scaleOut
Một phần tử trên giao diện người dùng thu nhỏ và biến mất khỏi khung hiển thị.
expandIn
Một phần tử trên giao diện người dùng mở rộng vào khung hiển thị từ một điểm trung tâm.
shrinkOut
Một phần tử trên giao diện người dùng thu nhỏ ra khỏi khung hiển thị về một điểm trung tâm.
expandHorizontally
Một phần tử trên giao diện người dùng mở rộng theo chiều ngang vào khung hiển thị.
shrinkHorizontally
Một thành phần trên giao diện người dùng thu nhỏ theo chiều ngang ra khỏi khung hiển thị.
expandVertically
Một phần tử trên giao diện người dùng mở rộng theo chiều dọc vào khung hiển thị.
shrinkVertically
Một thành phần trên giao diện người dùng thu nhỏ theo chiều dọc ra khỏi khung hiển thị.

AnimatedVisibility cũng cung cấp một biến thể nhận đối số MutableTransitionState. Biến thể này cho phép bạn kích hoạt một ảnh động ngay khi thành phần kết hợp AnimatedVisibility được thêm vào cây thành phần Compose. 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 bằng cách sử dụng 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)
    )
}

Để tìm hiểu thêm về cách sử dụng Transition để quản lý ảnh động, hãy xem phần Tạo ảnh động đồng thời cho nhiều thuộc tính bằng hiệu ứng chuyển đổi.

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

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

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 một phiên bản của ContentTransform bằng cách kết hợp đối tượng EnterTransition với đối tượng ExitTransition bằng cách sử dụng hàm infix with. Bạn có thể áp dụng SizeTransform cho đối tượng ContentTransform bằng cách gắn đối tượng đó 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 chuyển động cho hiệu ứng chuyển tiếp 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 hai bố cục

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

Compose cung cấp các đối tượng sửa đổi để tạo ảnh động cho những thay đổi cụ thể ngay trên các thành phần kết hợp.

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

Thành phần kết hợp màu xanh lục tạo ảnh động cho sự 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.