Compose có nhiều cơ chế ảnh động tích hợp sẵn và bạn có thể cảm thấy khó khăn khi chọn một cơ chế. Dưới đây là danh sách các trường hợp sử dụng ảnh động thường gặp. Để biết thêm thông tin chi tiết về toàn bộ các tuỳ chọn API khác nhau mà bạn có, hãy đọc tài liệu đầy đủ về Ảnh động trong Compose.
Tạo ảnh động cho các thuộc tính phổ biến của thành phần kết hợp
Compose cung cấp các API tiện lợi cho phép bạn giải quyết nhiều trường hợp sử dụng ảnh động thường gặp. Phần này minh hoạ cách bạn có thể tạo ảnh động cho các thuộc tính phổ biến của một thành phần kết hợp.
Tạo ảnh động cho quá trình xuất hiện / biến mất
Sử dụng AnimatedVisibility để ẩn hoặc hiện một Thành phần kết hợp. Các thành phần con bên trong AnimatedVisibility có thể sử dụng Modifier.animateEnterExit() cho quá trình chuyển đổi vào hoặc ra của riêng chúng.
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 // ... }
Các tham số vào và ra của AnimatedVisibility cho phép bạn định cấu hình cách một thành phần kết hợp hoạt động khi xuất hiện và biến mất. Đọc tài liệu
đầy đủ để biết thêm thông tin.
Một lựa chọn khác để tạo ảnh động cho khả năng hiển thị của một thành phần kết hợp là tạo ảnh động cho độ
mờ theo thời gian bằng cách sử dụng animateFloatAsState:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
Tuy nhiên, việc thay đổi độ mờ đi kèm với lưu ý rằng thành phần kết hợp vẫn nằm trong thành phần và tiếp tục chiếm không gian mà thành phần đó được bố trí. Điều này có thể khiến trình đọc màn hình và các cơ chế hỗ trợ tiếp cận khác vẫn coi mục đó là trên màn hình. Mặt khác, AnimatedVisibility cuối cùng sẽ xoá mục đó khỏi thành phần.
Tạo ảnh động cho màu nền
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Lựa chọn này có hiệu suất cao hơn so với việc sử dụng Modifier.background().
Modifier.background() có thể chấp nhận được đối với chế độ cài đặt màu một lần, nhưng khi tạo ảnh động cho màu theo thời gian, điều này có thể gây ra nhiều lần kết hợp lại hơn mức cần thiết.
Để tạo ảnh động vô hạn cho màu nền, hãy xem phần lặp lại ảnh động section.
Tạo ảnh động cho kích thước của một thành phần kết hợp
Compose cho phép bạn tạo ảnh động cho kích thước của các thành phần kết hợp theo một vài cách. Sử dụng
animateContentSize() cho ảnh động giữa các thay đổi về kích thước của thành phần kết hợp.
Ví dụ: nếu bạn có một hộp chứa văn bản có thể mở rộng từ một đến nhiều dòng, bạn có thể sử dụng Modifier.animateContentSize() để đạt được quá trình chuyển đổi mượt mà hơn:
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 } ) { }
Bạn cũng có thể sử dụng AnimatedContent với SizeTransform để mô tả
cách thay đổi kích thước.
Tạo ảnh động cho vị trí của thành phần kết hợp
Để tạo ảnh động cho vị trí của một thành phần kết hợp, hãy sử dụng Modifier.offset{ } kết hợp với animateIntOffsetAsState().
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
Nếu bạn muốn đảm bảo rằng các thành phần kết hợp không được vẽ chồng lên hoặc dưới các thành phần kết hợp khác khi tạo ảnh động cho vị trí hoặc kích thước, hãy sử dụng Modifier.layout{ }. Đối tượng sửa đổi này truyền các thay đổi về kích thước và vị trí cho thành phần mẹ, sau đó ảnh hưởng đến các thành phần con khác.
Ví dụ: nếu bạn đang di chuyển một Box trong một Column và các thành phần con khác cần di chuyển khi Box di chuyển, hãy đưa thông tin về độ lệch vào Modifier.layout{ } như sau:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }
Modifier.layout{ }Tạo ảnh động cho khoảng đệm của một thành phần kết hợp
Để tạo ảnh động cho khoảng đệm của một thành phần kết hợp, hãy sử dụng animateDpAsState kết hợp với Modifier.padding():
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
Tạo ảnh động cho độ cao của một thành phần kết hợp
Để tạo ảnh động cho độ cao của một thành phần kết hợp, hãy sử dụng animateDpAsState kết hợp với Modifier.graphicsLayer{ }. Đối với các thay đổi về độ cao một lần, hãy sử dụng Modifier.shadow(). Nếu bạn đang tạo ảnh động cho bóng, thì việc sử dụng đối tượng sửa đổi Modifier.graphicsLayer{ } là lựa chọn có hiệu suất cao hơn.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
Ngoài ra, hãy sử dụng thành phần kết hợp Card và đặt thuộc tính độ cao thành
các giá trị khác nhau cho mỗi trạng thái.
Tạo ảnh động cho tỷ lệ, phép dịch hoặc phép xoay văn bản
Khi tạo ảnh động cho tỷ lệ, phép dịch hoặc phép xoay văn bản, hãy đặt textMotion tham số trên TextStyle thành TextMotion.Animated. Điều này đảm bảo quá trình chuyển đổi mượt mà hơn giữa các ảnh động văn bản. Sử dụng Modifier.graphicsLayer{ } để
dịch, xoay hoặc điều chỉnh tỷ lệ văn bản.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
Tạo ảnh động cho màu văn bản
Để tạo ảnh động cho màu văn bản, hãy sử dụng hàm lambda color trên thành phần kết hợp BasicText:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
Chuyển đổi giữa các loại nội dung
Sử dụng AnimatedContent để tạo ảnh động giữa các thành phần kết hợp. Nếu bạn chỉ muốn có hiệu ứng mờ dần tiêu chuẩn giữa các thành phần kết hợp, hãy sử dụng Crossfade.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
Bạn có thể tuỳ chỉnh AnimatedContent để hiển thị nhiều loại hiệu ứng chuyển đổi vào và ra. Để biết thêm thông tin, hãy đọc tài liệu về
AnimatedContent hoặc đọc bài đăng trên blog này về
AnimatedContent.
Tạo ảnh động trong khi di chuyển đến các đích đến khác nhau
Để tạo ảnh động cho quá trình chuyển đổi giữa các thành phần kết hợp khi sử dụng cấu phần phần mềm
navigation-compose, hãy chỉ định enterTransition và
exitTransition trên một thành phần kết hợp. Bạn cũng có thể đặt ảnh động mặc định để sử dụng cho tất cả các đích đến ở cấp cao nhất NavHost:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
Có nhiều loại hiệu ứng chuyển đổi vào và ra áp dụng các hiệu ứng khác nhau cho nội dung đến và đi. Hãy xem tài liệu để biết thêm thông tin.
Lặp lại ảnh động
Sử dụng rememberInfiniteTransition với infiniteRepeatable
animationSpec để liên tục lặp lại ảnh động. Thay đổi RepeatModes để chỉ định cách ảnh động này sẽ di chuyển qua lại.
Sử dụng repeatable để lặp lại một số lần.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
Bắt đầu ảnh động khi khởi chạy một thành phần kết hợp
LaunchedEffect chạy khi một thành phần kết hợp tham gia thành phần. Ảnh động này bắt đầu khi khởi chạy một thành phần kết hợp. Bạn có thể sử dụng ảnh động này để điều khiển sự thay đổi trạng thái của ảnh động. Sử dụng Animatable với phương thức animateTo để bắt đầu ảnh động khi khởi chạy:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Tạo ảnh động tuần tự
Sử dụng các API coroutine Animatable để thực hiện ảnh động tuần tự hoặc đồng thời. Việc gọi animateTo trên Animatable lần lượt khiến mỗi ảnh động phải đợi các ảnh động trước đó hoàn tất trước khi tiếp tục .
Nguyên nhân là vì đây là một hàm tạm ngưng.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Tạo ảnh động đồng thời
Sử dụng các API coroutine (Animatable#animateTo() hoặc animate) hoặc
API Transition để tạo ảnh động đồng thời. Nếu bạn sử dụng nhiều hàm khởi chạy trong ngữ cảnh coroutine, thì các hàm này sẽ khởi chạy ảnh động cùng một lúc:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Bạn có thể sử dụng API updateTransition để sử dụng cùng một trạng thái nhằm điều khiển
nhiều ảnh động thuộc tính khác nhau cùng một lúc. Ví dụ bên dưới tạo ảnh động cho hai thuộc tính được kiểm soát bằng sự thay đổi trạng thái, rect và borderWidth:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Tối ưu hoá hiệu suất ảnh động
Ảnh động trong Compose có thể gây ra vấn đề về hiệu suất. Nguyên nhân là do bản chất của ảnh động: di chuyển hoặc thay đổi các pixel trên màn hình một cách nhanh chóng, từng khung hình để tạo ảo giác về chuyển động.
Hãy cân nhắc các giai đoạn khác nhau của Compose: thành phần, bố cục và vẽ. Nếu ảnh động của bạn thay đổi giai đoạn bố cục, thì ảnh động đó sẽ yêu cầu tất cả các thành phần kết hợp bị ảnh hưởng phải bố trí lại và vẽ lại. Nếu ảnh động của bạn xảy ra trong giai đoạn vẽ, thì theo mặc định, ảnh động đó sẽ có hiệu suất cao hơn so với khi bạn chạy ảnh động trong giai đoạn bố cục, vì ảnh động đó sẽ có ít việc phải làm hơn.
Để đảm bảo ứng dụng của bạn thực hiện ít nhất có thể trong khi tạo ảnh động, hãy chọn phiên bản lambda của Modifier nếu có thể. Điều này sẽ bỏ qua quá trình kết hợp lại và thực hiện
ảnh động bên ngoài giai đoạn thành phần. Nếu không, hãy sử dụng
Modifier.graphicsLayer{ }, vì đối tượng sửa đổi này luôn chạy trong giai đoạn vẽ
. Để biết thêm thông tin về vấn đề này, hãy xem phần trì hoãn việc đọc trong
tài liệu về hiệu suất.
Thay đổi thời gian ảnh động
Theo mặc định, Compose sử dụng ảnh động lò xo cho hầu hết các ảnh động. Lò xo hoặc ảnh động dựa trên vật lý mang lại cảm giác tự nhiên hơn. Các ảnh động này cũng có thể bị gián đoạn vì chúng tính đến vận tốc hiện tại của đối tượng thay vì thời gian cố định.
Nếu bạn muốn ghi đè giá trị mặc định, thì tất cả các API ảnh động được minh hoạ ở trên đều có khả năng đặt animationSpec để tuỳ chỉnh cách chạy ảnh động, cho dù bạn muốn ảnh động thực thi trong một khoảng thời gian nhất định hay có độ đàn hồi cao hơn.
Sau đây là bản tóm tắt các tuỳ chọn animationSpec khác nhau:
spring: Ảnh động dựa trên vật lý, mặc định cho tất cả ảnh động. Bạn có thể thay đổi độ cứng hoặc dampingRatio để đạt được giao diện và cảm giác khác cho ảnh động.tween(viết tắt của between): Ảnh động dựa trên thời lượng, tạo ảnh động giữa hai giá trị bằng hàmEasing.keyframes: Thông số kỹ thuật để chỉ định các giá trị tại một số điểm chính trong ảnh động.repeatable: Thông số kỹ thuật dựa trên thời lượng chạy một số lần, được chỉ định bởiRepeatMode.infiniteRepeatable: Thông số kỹ thuật dựa trên thời lượng chạy mãi mãi.snap: Chụp nhanh ngay lập tức đến giá trị cuối mà không có ảnh động.
Đọc tài liệu đầy đủ để biết thêm thông tin về animationSpecs.
Tài nguyên khác
Để biết thêm ví dụ về ảnh động thú vị trong Compose, hãy xem các ví dụ sau:
- 5 ảnh động nhanh trong Compose
- Making Jellyfish move in Compose (Tạo ảnh động hình con sứa trong Compose)
- Tuỳ chỉnh
AnimatedContenttrong Compose - Làm quen với các hàm Easing trong Compose