Compose có nhiều cơ chế ảnh động tích hợp 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 phổ biến cho ảnh động. Để biết thêm thông tin chi tiết về toàn bộ các lựa chọn API mà bạn có thể sử dụng, hãy đọc toàn bộ tài liệu về Ảnh động trong Compose.
Tạo ảnh động cho các thuộc tính kết hợp phổ biến
Compose cung cấp các API thuận tiện giúp bạn giải quyết nhiều trường hợp sử dụng ảnh động phổ biến. Phần này minh hoạ cách bạn có thể tạo hiệu ứ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 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 nhập hoặc thoát của riêng mình.
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ố nhập và thoát của AnimatedVisibility
cho phép bạn định cấu hình cách hoạt động của một thành phần kết hợp khi thành phần đó xuất hiện và biến mất. Hãy đọ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 giá trị alpha 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 giá trị alpha có một điểm hạn chế là 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 hiệu ứ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 hiệu ứ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.
Tạo hiệu ứ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 số 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 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ể dùng Modifier.animateContentSize()
để đạt được hiệu ứng 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 hiệu ứ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ẽ lên trên hoặc bên dưới các thành phần kết hợp khác khi tạo hiệu ứ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 thêm thông tin về độ lệch bằng 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 hiệu ứ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 độ nâng của một thành phần kết hợp
Để tạo hiệu ứng cho độ nâng 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 những thay đổi về độ cao chỉ xảy ra một lần, hãy sử dụng Modifier.shadow()
. Nếu bạn đang tạo hiệu ứng đổ bóng, thì việc sử dụng đối tượng sửa đổi Modifier.graphicsLayer{ }
là lựa chọn hiệu quả 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 dùng thành phần kết hợp Card
và đặt thuộc tính độ nâng thành các giá trị khác nhau cho mỗi trạng thái.
Tạo hiệu ứng chuyển động cho tỷ lệ, bản dịch hoặc hướng xoay của văn bản

Khi tạo hiệu ứng cho tỷ lệ, bản dịch hoặc xoay văn bản, hãy đặt tham số textMotion
trên TextStyle
thành TextMotion.Animated
. Điều này giúp đảm bảo hiệu ứng chuyển đổi mượt mà hơn giữa các ảnh động văn bản. 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 hiệu ứng chuyển động cho màu văn bản

Để tạo hiệu ứng chuyển động cho màu văn bản, hãy 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 hiệu ứng chuyển động giữa các thành phần kết hợp khác nhau. Nếu bạn chỉ muốn hiệu ứng mờ 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() } } }
AnimatedContent
có thể được tuỳ chỉnh để hiển thị nhiều loại hiệu ứng chuyển tiếp 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 này trên blog về
AnimatedContent
.
Tạo ảnh động trong khi di chuyển đến các đích đến khác nhau

Để tạo hiệu ứng chuyển đổi dạng ảnh động 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ẽ được 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 cảnh khi vào và khi ra, áp dụng các hiệu ứng khác nhau cho nội dung đến và nội dung đ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 chuyển động qua lại.
Sử dụng finiteRepeatable
để lặp lại một số lần nhất định.
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 một ả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. Thao tác này sẽ bắt đầu một ảnh động khi khởi chạy một thành phần kết hợp. Bạn có thể dùng thao tác này để điều khiển thay đổi trạng thái ảnh động. Sử dụng Animatable
với phương thức animateTo
để bắt đầu hiệu ứ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 API coroutine Animatable
để thực hiện các ả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 ảnh động trước đó hoàn tất rồi mới tiếp tục .
Nguyên nhân là do đâ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 được hiệu ứng chuyển động đồng thời. Nếu bạn sử dụng nhiều hàm khởi chạy trong một 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 minh hoạ hai thuộc tính được kiểm soát bằng một thay đổi về 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. Điều này là do bản chất của ảnh động: các pixel di chuyển hoặc thay đổi nhanh chóng trên màn hình, 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à bản vẽ. Nếu ảnh động của bạn thay đổi giai đoạn bố cục, thì tất cả các thành phần kết hợp chịu ảnh hưởng đều phải bố trí lại và vẽ lại. Nếu ảnh động của bạn xuất hiện trong giai đoạn vẽ, thì theo mặc định, ảnh động đó sẽ hoạt động hiệu quả 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 thao tác nhất có thể trong khi tạo hiệu ứng chuyển động, hãy chọn phiên bản lambda của một Modifier
(nếu có thể). Thao tác 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 kết hợp, 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 hoãn đọc trong tài liệu về hiệu suất.
Thay đổi thời gian cho ảnh động
Theo mặc định, Compose sử dụng ảnh động spring cho hầu hết các ảnh động. Ảnh động dựa trên lực lò xo hoặc vật lý sẽ mang lại cảm giác tự nhiên hơn. Chúng 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ì một thời gian cố định.
Nếu bạn muốn ghi đè giá trị mặc định, tất cả các API hoạt ảnh được minh hoạ ở trên đều có khả năng đặt một animationSpec
để tuỳ chỉnh cách chạy một ả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ảy cao hơn.
Sau đây là nội dung tóm tắt về các lựa chọn animationSpec
:
spring
: Ảnh động dựa trên vật lý, là ảnh động 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 nhận khác về hiệu ứng chuyển động.tween
(viết tắt của between – ở giữa): Ả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ố để 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 nhất định, doRepeatMode
chỉ định.infiniteRepeatable
: Thông số dựa trên thời lượng chạy mãi mãi.snap
: Chuyển ngay sang giá trị cuối mà không có ảnh động.

Hãy đọc toàn bộ tài liệu để biết thêm thông tin về animationSpecs.
Tài nguyên khác
Để xem 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
AnimatedContent
trong Compose - Làm quen với hàm Easing trong Compose