Compose có nhiều cơ chế ảnh động tích hợp sẵn và có thể gây choáng ngợp biết nên chọn cái nào. Dưới đây là danh sách các trường hợp sử dụng ảnh động phổ biến. Để thông tin chi tiết hơn về tập hợp đầy đủ các tuỳ chọn API hiện có dành cho bạn, hãy đọc toàn bộ tài liệu về Ảnh động 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 vấn đề phổ biến các trường hợp sử dụng ảnh động. Phần này trình bày cách bạn có thể tạo ảnh động phổ biến các thuộc tính của một thành phần kết hợp.
Ảnh động xuất hiện / biến mất
Sử dụng AnimatedVisibility
để ẩn hoặc hiển thị một Thành phần kết hợp. Có trẻ em bên trong
AnimatedVisibility
có thể sử dụng Modifier.animateEnterExit()
để nhập riêng
hoặc thoát khỏi quá trình chuyển đổi.
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
một thành phần kết hợp sẽ hoạt động khi nó xuất hiện rồi biến mất. Đọc toàn bộ
để biết thêm thông tin.
Một cách khác để tạo ảnh động cho chế độ hiển thị của thành phần kết hợp là tạo ảnh động cho
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 alpha đi kèm với một cảnh báo rằng thành phần kết hợp vẫn được giữ lại
trong cấu trúc và tiếp tục chiếm không gian đã bố trí. Chiến dịch 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 cân nhắc
mục đó trên màn hình. Mặt khác, cuối cùng AnimatedVisibility
sẽ xoá
mục từ cấu trúc.
Tạo ảnh động màu nền
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Tuỳ chọn này hiệu quả hơn so với việc sử dụng Modifier.background()
.
Modifier.background()
có thể được chấp nhận đối với chế độ cài đặt màu chụp một lần, nhưng khi
khi tạo ảnh động màu theo thời gian, điều này có thể dẫn đến việc kết hợp lại nhiều hơn so với
nếu cần.
Để tạo ảnh động vô hạn cho màu nền, hãy xem lặp lại ảnh động .
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()
đối với ảnh động giữa các lần thay đổi kích thước thành phần kết hợp.
Ví dụ: nếu bạn có hộp chứa văn bản có thể mở rộng từ 1 sang
bạn có thể sử dụng Modifier.animateContentSize()
để có được kết quả tốt hơn
chuyển đổi:
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
cùng 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 bị vẽ lên trên hoặc xuống dưới các
các thành phần kết hợp khi tạo ảnh động cho vị trí hoặc kích thước, hãy sử dụng Modifier.layout{ }
. Chiến dịch này
đối tượng sửa đổi sẽ truyền các thay đổi về kích thước và vị trí đến thành phần mẹ, sau đó ảnh hưởng đến
những trẻ khác.
Ví dụ: nếu bạn đang di chuyển Box
trong 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 với
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) ) }
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 nâng cao cho thành phần kết hợp
Để tạo ảnh độ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 các thay đổi độ cao một lần duy nhất, hãy sử dụng
Modifier.shadow()
. Nếu bạn đang tạo ảnh động đổ bóng, hãy sử dụng
Đối tượng sửa đổi Modifier.graphicsLayer{ }
là tuỳ 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, bạn có thể sử dụng thành phần kết hợp Card
và thiết lập 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 điều chỉnh tỷ lệ văn bản, bản dịch hoặc xoay
Khi tạo ảnh động cho tỷ lệ, bản dịch hoặc xoay văn bản, hãy đặt giá trị textMotion
từ TextStyle
đến TextMotion.Animated
. Thao tác này đảm bảo mượt mà hơn
hiệu ứng chuyển tiếp giữa các hoạt ảnh 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 hiệu ứng làm 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 kiểu nhập và nhập
chuyển đổi thoát. Để 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
AnimatedContent
.
Tạo ảnh động trong khi điều hướng đến nhiều đích đến
Để tạo hiệu ứng chuyển đổi giữa các thành phần kết hợp khi sử dụng thuộc tính
Cấu phần phần mềm navigation-compose, 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 thành
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 chuyển đổi nhập và thoát khác nhau áp dụng các hiệu ứng khác nhau đối với nội dung đến và đi, hãy xem tài liệu khác để 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
thành
chỉ định cách chuyển đổi 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 tạo ả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 vào cấu trúc. Sự kiện bắt đầu
ảnh động 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 ảnh động
thay đổi trạng thái. 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 tuần tự hoặc đồng thời
ảnh động. Lần lượt gọi animateTo
trên Animatable
vì các nguyên nhân khác
từng ảnh động để chờ các ảnh động trước đó hoàn tất trước khi tiếp tục .
Lý do là vì đây là 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
Dùng các API coroutine (Animatable#animateTo()
hoặc animate
) hoặc
API Transition
để đạt được ảnh động đồng thời. Nếu bạn sử dụng nhiều
các hàm khởi chạy trong ngữ cảnh coroutine, đồng thời khởi chạy các ảnh động
thời gian:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Bạn có thể dùng API updateTransition
để dùng cùng một trạng thái cho việc lái xe
nhiều ảnh động thuộc tính khác nhau cùng một lúc. Ví dụ bên dưới về ảnh động
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. Điều này là do bản chất ảnh động là gì: 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 xem xét các giai đoạn khác nhau của Compose: bố cục, bố cục và vẽ. Nếu ảnh động sẽ thay đổi giai đoạn bố cục, nó đòi hỏi tất cả các thành phần kết hợp bị ảnh hưởng bố cục lại và vẽ lại. Nếu ảnh động xuất hiện trong giai đoạn vẽ, thì đó là mặc định sẽ hiệu quả hơn so với khi bạn chạy ảnh động trong bố cục vì nhìn chung sẽ có ít công việc phải làm hơn.
Để đảm bảo ứng dụng của bạn hoạt động ít nhất có thể trong khi tạo ảnh động, hãy chọn hàm lambda
phiên bản của Modifier
khi có thể. Thao tác này bỏ qua việc kết hợp lại và thực hiện
ảnh động 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 bản vẽ
pha. Để biết thêm thông tin về vấn đề này, hãy xem phần lượt đọc trì hoãn 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 mùa xuân cho hầu hết ảnh động. Suối, hoặc
hoạt ảnh dựa trên vật lý, tạo cảm giác tự nhiên hơn. Chúng cũng có thể gây gián đoạn vì
chúng xem xét vận tốc hiện tại của vật thể thay vì một khoảng thời gian cố định.
Nếu bạn muốn ghi đè giá trị mặc định, tất cả API ảnh động được trình bày ở trên
có thể đặt animationSpec
để tuỳ chỉnh cách chạy ảnh động,
liệu bạn muốn thực thi trong một khoảng thời gian nhất định hay sôi nổi hơn.
Sau đây là nội dung tóm tắt về các tuỳ chọn animationSpec
:
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 để có được ảnh động khác giao diện.tween
(viết tắt của giữa): Ảnh động dựa trên thời lượng, ảnh động giữa hai giá trị bằng hàmEasing
.keyframes
: Quy cách để chỉ định giá trị tại các điểm chính nhất đị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ố kỹ thuật dựa trên thời lượng chạy vĩnh viễn.snap
: Điều chỉnh ngay giá trị kết thúc mà không cần bất kỳ ảnh động nào.
Hãy đọ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
AnimatedContent
trong Compose - Làm quen với các hàm Easing trong Compose