Tuỳ chỉnh hiệu ứng chuyển đổi thành phần dùng chung

Để tuỳ chỉnh cách chạy ảnh động chuyển đổi phần tử dùng chung, bạn có thể dùng một số tham số để thay đổi cách chuyển đổi của các phần tử dùng chung.

Thông số kỹ thuật của ảnh động

Để thay đổi thông số ảnh động dùng cho chuyển động kích thước và vị trí, bạn có thể chỉ định tham số boundsTransform khác trên Modifier.sharedElement(). Điều này cung cấp vị trí Rect ban đầu và vị trí Rect mục tiêu.

Ví dụ: để làm cho văn bản trong ví dụ trước di chuyển theo chuyển động vòng cung, hãy chỉ định tham số boundsTransform để sử dụng thông số kỹ thuật keyframes:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

Bạn có thể sử dụng bất kỳ AnimationSpec nào. Ví dụ này sử dụng thông số kỹ thuật keyframes.

Hình 1. Ví dụ thể hiện nhiều tham số boundsTransform

Chế độ đổi kích thước

Khi tạo ảnh động giữa hai giới hạn dùng chung, bạn có thể đặt tham số resizeMode thành RemeasureToBounds hoặc ScaleToBounds. Tham số này xác định cách các phần tử dùng chung chuyển đổi giữa hai trạng thái. Trước tiên, ScaleToBounds đo lường bố cục con bằng các quy tắc ràng buộc xem trước (hoặc mục tiêu). Sau đó, bố cục ổn định của bố cục con sẽ được điều chỉnh theo tỷ lệ để vừa với giới hạn dùng chung. Bạn có thể xem ScaleToBounds là "tỷ lệ đồ hoạ" giữa các trạng thái.

Trong khi đó, RemeasureToBounds đo lường và sắp xếp lại bố cục con của sharedBounds với các điều kiện ràng buộc cố định ở dạng động dựa trên kích thước mục tiêu. Quá trình đo lường lại được kích hoạt khi có sự thay đổi về kích thước giới hạn, có thể là mọi khung hình.

Đối với thành phần kết hợp Text, bạn nên sử dụng ScaleToBounds vì thành phần này sẽ tránh việc bố cục lại và chỉnh lại luồng văn bản trên các dòng khác nhau. Đối với các giới hạn có tỷ lệ khung hình khác nhau và nếu muốn tính liên tục linh hoạt giữa 2 phần tử dùng chung, bạn nên sử dụng RemeasureToBounds.

Bạn có thể thấy sự khác biệt giữa hai chế độ đổi kích thước trong các ví dụ sau:

ScaleToBounds

RemeasureToBounds

Chuyển đến bố cục cuối cùng

Theo mặc định, khi chuyển đổi giữa hai bố cục, kích thước bố cục sẽ tạo hiệu ứng động giữa trạng thái bắt đầu và trạng thái cuối cùng. Đây có thể là hành vi không mong muốn khi tạo ảnh động cho nội dung, chẳng hạn như văn bản.

Ví dụ sau minh hoạ văn bản mô tả "Lorem Ipsum" nhập màn hình theo 2 cách. Ví dụ đầu tiên, văn bản chỉnh lại luồng khi vào khi vùng chứa tăng kích thước, ví dụ thứ hai văn bản không chỉnh lại luồng khi tăng kích thước. Việc thêm Modifier.skipToLookaheadSize() sẽ ngăn chỉnh lại luồng khi nó phát triển.

Không có Modifier.skipToLookahead() – chú ý chỉnh lại luồng văn bản "Lorem Ipsum"

Modifier.skipToLookahead() – lưu ý rằng văn bản "Lorem Ipsum" giữ trạng thái cuối cùng ở đầu ảnh động

Phần cắt và lớp phủ

Một khái niệm quan trọng khi tạo các thành phần dùng chung trong Compose là để các thành phần này có thể chia sẻ giữa các thành phần kết hợp, hoạt động kết xuất thành phần kết hợp được nâng lên thành một lớp phủ khi quá trình chuyển đổi bắt đầu phù hợp với đích đến đó. Ảnh hưởng của việc này là nó sẽ thoát khỏi ranh giới của thành phần mẹ và các phép biến đổi lớp của thành phần mẹ (ví dụ: alpha và tỷ lệ).

Thành phần này sẽ hiển thị bên trên các thành phần giao diện người dùng không được chia sẻ khác. Sau khi quá trình chuyển đổi hoàn tất, thành phần này sẽ được xoá từ lớp phủ sang DrawScope của chính nó.

Để cắt một phần tử dùng chung thành một hình dạng, hãy sử dụng hàm Modifier.clip() tiêu chuẩn. Đặt đoạn mã này sau sharedElement():

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

Nếu cần đảm bảo rằng một phần tử dùng chung không bao giờ hiển thị bên ngoài vùng chứa mẹ, bạn có thể đặt clipInOverlayDuringTransition trên sharedElement(). Theo mặc định, đối với các giới hạn chia sẻ lồng nhau, clipInOverlayDuringTransition sử dụng đường dẫn cắt từ sharedBounds() mẹ.

Để hỗ trợ giữ cho các thành phần cụ thể trên giao diện người dùng (chẳng hạn như thanh dưới cùng hoặc nút hành động nổi) luôn ở trên cùng trong khi chuyển đổi thành phần dùng chung, hãy sử dụng Modifier.renderInSharedTransitionScopeOverlay(). Theo mặc định, đối tượng sửa đổi này giữ nội dung trong lớp phủ trong thời gian hiệu ứng chuyển đổi được chia sẻ đang hoạt động.

Ví dụ: trong Jetsnack, BottomAppBar cần được đặt lên đầu phần tử dùng chung cho đến khi màn hình không hiển thị. Việc thêm đối tượng sửa đổi vào thành phần kết hợp sẽ giúp nâng cao đối tượng này.

Không có Modifier.renderInSharedTransitionScopeOverlay()

Bằng Modifier.renderInSharedTransitionScopeOverlay()

Đôi khi, bạn có thể muốn thành phần kết hợp không được chia sẻ tạo hiệu ứng động cũng như giữ nguyên trên các thành phần kết hợp khác trước khi chuyển đổi. Trong những trường hợp như vậy, hãy sử dụng renderInSharedTransitionScopeOverlay().animateEnterExit() để tạo hiệu ứng ảnh động cho thành phần kết hợp khi hiệu ứng chuyển đổi thành phần được chia sẻ chạy:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

Hình 2.Thanh ứng dụng ở dưới cùng trượt vào và ra khi chuyển đổi ảnh động

Trong trường hợp hiếm gặp, khi bạn không muốn phần tử được chia sẻ của mình hiển thị trong lớp phủ, bạn có thể đặt renderInOverlayDuringTransition trên sharedElement() thành false.

Thông báo cho các bố cục đồng cấp về các thay đổi đối với kích thước của phần tử dùng chung

Theo mặc định, sharedBounds()sharedElement() không thông báo cho vùng chứa mẹ về bất kỳ thay đổi nào về kích thước khi chuyển đổi bố cục.

Để áp dụng các thay đổi về kích thước cho vùng chứa mẹ khi vùng chứa này chuyển đổi, hãy thay đổi tham số placeHolderSize thành PlaceHolderSize.animatedSize. Làm như vậy sẽ khiến mục tăng lên hoặc thu nhỏ. Tất cả các mục khác trong bố cục sẽ phản hồi với thay đổi.

PlaceholderSize.contentSize (mặc định)

PlaceholderSize.animatedSize

(Chú ý đến cách các mục khác trong danh sách di chuyển xuống tương ứng với việc mục này tăng lên)