Trang này mô tả cách tạo ảnh động dựa trên giá trị trong Jetpack Compose, tập trung vào các API tạo ảnh động cho giá trị dựa trên trạng thái hiện tại và trạng thái mục tiêu của giá trị đó.
Tạo ảnh động cho một giá trị duy nhất bằng animate*AsState
Các hàm animate*AsState
là các API ảnh động đơn giản trong Compose để tạo ảnh động cho một giá trị duy nhất. Bạn chỉ cung cấp giá trị đích (hoặc giá trị cuối) và API sẽ bắt đầu tạo ảnh động từ giá trị hiện tại đến giá trị được chỉ định.
Ví dụ sau đây tạo ảnh động alpha bằng API này. Bằng cách gói giá trị mục tiêu trong animateFloatAsState
, giá trị alpha giờ là giá trị ảnh động giữa các giá trị đã cung cấp (1f
hoặc 0.5f
trong trường hợp này).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
Bạn không cần tạo một phiên bản của bất kỳ lớp ảnh động nào hoặc xử lý gián đoạn. Trong trường hợp này, một đối tượng ảnh động (cụ thể là một thực thể Animatable
) sẽ được tạo và ghi nhớ tại nơi hàm được gọi, với giá trị mục tiêu đầu tiên chính là giá trị ban đầu. Kể từ đó, bất cứ khi nào bạn cung cấp cho thành phần kết hợp này một giá trị mục tiêu khác, hệ thống sẽ tự động bắt đầu một ảnh động theo giá trị đó. Nếu đã có ảnh động trong giai đoạn hiển thị, thì ảnh động sẽ bắt đầu từ giá trị hiện tại (và vận tốc) và tạo ảnh động hướng tới giá trị mục tiêu. Trong quá trình
ảnh động, thành phần kết hợp này sẽ được ghép lại và trả về một giá trị ảnh động
được cập nhật cho mọi khung hình.
Theo mặc định, Compose cung cấp các hàm animate*AsState
cho Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
và IntSize
. Bạn có thể thêm tính năng hỗ trợ cho các loại dữ liệu khác bằng cách cung cấp TwoWayConverter
cho animateValueAsState
nhận một loại chung.
Bạn có thể sử dụng AnimationSpec
để tuỳ chỉnh thông số kỹ thuật của ảnh động. Hãy xem AnimationSpec
để biết thêm thông tin.
Tạo ảnh động đồng thời cho nhiều thuộc tính bằng hiệu ứng chuyển đổi
Transition
quản lý một hoặc nhiều ảnh động dưới dạng thành phần con và chạy đồng thời giữa nhiều trạng thái.
Các trạng thái có thể thuộc bất kỳ loại dữ liệu nào. Trong nhiều trường hợp, bạn có thể sử dụng loại enum
tuỳ chỉnh để xác minh tính an toàn của loại, như trong ví dụ này:
enum class BoxState { Collapsed, Expanded }
updateTransition
tạo và ghi nhớ một thực thể của Transition
và cập nhật trạng thái của thực thể đó.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Bạn có thể sử dụng một trong các hàm mở rộng animate*
để xác định ảnh động
bố cục con trong hiệu ứng chuyển đổi này. Chỉ định các giá trị mục tiêu cho mỗi trạng thái.
Các hàm animate*
này trả về một giá trị ảnh động. Mọi khung hình đều cập nhật
trong suốt chế độ ảnh động khi trạng thái chuyển đổi được cập nhật với
updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Bạn có thể chuyển tham số transitionSpec
để chỉ định một AnimationSpec
khác cho mỗi kiểu kết hợp của các thay đổi trạng thái chuyển đổi. Hãy xem AnimationSpec
để biết thêm thông tin.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
Khi hiệu ứng chuyển đổi đạt đến trạng thái mục tiêu, Transition.currentState
sẽ giống với Transition.targetState
. Bạn có thể sử dụng trạng thái này như một tín hiệu cho biết hiệu ứng chuyển đổi đã hoàn tất hay chưa.
Đôi khi, bạn có thể muốn có một trạng thái ban đầu khác với trạng thái mục tiêu đầu tiên. Bạn có thể sử dụng updateTransition
với MutableTransitionState
để đạt được điều này. Ví dụ: bạn có thể khởi động ảnh động ngay khi mã nhập vào phương thức hợp thành.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Đối với quá trình chuyển đổi phức tạp hơn liên quan đến nhiều hàm có khả năng kết hợp, bạn có thể sử dụng createChildTransition
để tạo nội dung chuyển đổi con. Kỹ thuật này dùng để phân biệt các mối lo ngại giữa nhiều thành phần phụ trong một thành phần kết hợp phức tạp. Quá trình chuyển đổi thành phần mẹ sẽ nhận ra được tất cả các giá trị ảnh động trong các hiệu ứng chuyển đổi thành phần con.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Sử dụng hiệu ứng chuyển đổi với AnimatedVisibility
và AnimatedContent
AnimatedVisibility
và AnimatedContent
có thể sử dụng dưới dạng các hàm mở rộng của Transition
. targetState
cho Transition.AnimatedVisibility
và Transition.AnimatedContent
có nguồn gốc từ Transition
và kích hoạt ảnh động nhập, thoát và sizeTransform
khi cần thiết khi targetState
của Transition
thay đổi. Các hàm mở rộng này cho phép bạn nâng tất cả ảnh động enter, exit và sizeTransform
mà nếu không nội bộ bên trong AnimatedVisibility
/AnimatedContent
sẽ được nâng lên thành Transition
. Bạn có thể ghi nhận từ bên ngoài sự thay đổi trạng thái của AnimatedVisibility
/AnimatedContent
ở các hàm mở rộng này. Thay vì tham số visible
boolean, phiên bản này của AnimatedVisibility
sẽ lấy một hàm lambda chuyển đổi trạng thái mục tiêu của lệnh chuyển đổi thành phần mẹ thành một boolean.
Hãy xem AnimatedVisibility
và AnimatedContent
để biết thông tin chi tiết.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Đóng gói hiệu ứng chuyển đổi và làm cho nó tái sử dụng được
Đối với các trường hợp sử dụng đơn giản, việc xác định ảnh động chuyển đổi trong cùng một thành phần kết hợp như giao diện người dùng là một lựa chọn hợp lệ. Tuy nhiên, khi thao tác trên một thành phần phức tạp có một số giá trị được tạo ảnh động, bạn có thể muốn tách riêng việc triển khai ảnh động với giao diện người dùng có thể kết hợp.
Bạn có thể thực hiện việc này bằng cách tạo một lớp chứa tất cả các giá trị ảnh động và hàm update
để trả về một thực thể của lớp đó. Bạn có thể trích xuất quá trình triển khai chuyển đổi vào hàm riêng biệt mới. Mẫu này rất hữu ích khi bạn cần tập trung logic ảnh động hoặc làm cho các ảnh động phức tạp có thể sử dụng lại.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Tạo ảnh động lặp lại vô hạn bằng rememberInfiniteTransition
InfiniteTransition
lưu giữ một hoặc nhiều ảnh động bố cục con như Transition
, nhưng các ảnh động bắt đầu chạy ngay khi chúng vào cấu trúc và không dừng lại trừ phi bạn xoá chúng. Bạn có thể tạo một phiên bản của InfiniteTransition
bằng rememberInfiniteTransition
và thêm ảnh động con bằng animateColor
, animatedFloat
hoặc animatedValue
. Bạn cũng cần chỉ định một infiniteRepeatable
để chỉ định thông số kỹ thuật của ảnh động.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
API ảnh động cấp thấp
Tất cả các API ảnh động cấp cao đã đề cập trong phần trước đều được xây dựng dựa trên các API ảnh động cấp thấp.
Các hàm animate*AsState
là các API đơn giản để hiển thị thay đổi giá trị tức thì dưới dạng giá trị ảnh động. Chức năng này được hỗ trợ bởi Animatable
, một API dựa trên coroutine để tạo ảnh động cho một giá trị duy nhất.
updateTransition
tạo một đối tượng chuyển đổi có thể quản lý nhiều giá trị ảnh động và chạy các giá trị đó khi trạng thái thay đổi. rememberInfiniteTransition
cũng tương tự, nhưng hàm này tạo một hiệu ứng chuyển đổi vô hạn có thể quản lý nhiều ảnh động tiếp tục chạy vô thời hạn. Tất cả các API này đều là thành phần kết hợp ngoại trừ Animatable
, nghĩa là bạn có thể tạo các ảnh động này bên ngoài phương thức hợp thành.
Tất cả các API này đều dựa trên API Animation
nền tảng hơn. Mặc dù hầu hết các ứng dụng sẽ không tương tác trực tiếp với Animation
, nhưng bạn có thể truy cập vào một số khả năng tuỳ chỉnh của Animation
thông qua các API cấp cao hơn. Hãy xem bài viết Tuỳ chỉnh ảnh động để biết thêm thông tin về AnimationVector
và AnimationSpec
.
Animatable
: Tạo ảnh động cho một giá trị duy nhất dựa trên coroutine
Animatable
là trình lưu giữ giá trị có thể tạo ảnh động cho giá trị khi được thay đổi bằng animateTo
. Đây là API đang sao lưu nội dung triển khai animate*AsState
. Giao diện này đảm bảo tiếp tục duy trì tính nhất quán và loại trừ lẫn nhau, nghĩa là việc thay đổi giá trị luôn diễn ra liên tục và Compose sẽ huỷ mọi ảnh động đang diễn ra.
Nhiều tính năng của Animatable
, bao gồm cả animateTo
, là các hàm tạm ngưng.
Điều này có nghĩa là bạn phải gói các tính năng này trong một phạm vi coroutine thích hợp. Ví dụ: bạn có thể sử dụng thành phần kết hợp LaunchedEffect
để tạo một phạm vi chỉ trong khoảng thời gian của khoá-giá trị được chỉ định.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
Trong ví dụ trước, bạn tạo và ghi nhớ một thực thể của Animatable
với giá trị ban đầu là Color.Gray
. Tuỳ thuộc vào giá trị của cờ boolean ok
, màu sắc kết hợp ảnh động cho một trong hai hàm Color.Green
hoặc Color.Red
. Bất kỳ thay đổi nào tiếp theo đối với giá trị boolean sẽ bắt đầu chuyển ảnh động thành màu khác.
Nếu một ảnh động đang diễn ra khi giá trị thay đổi, thì Compose sẽ huỷ ảnh động đó và ảnh động mới sẽ bắt đầu từ giá trị hiện tại của ảnh chụp nhanh với vận tốc hiện tại.
API Animatable
này là phương thức triển khai cơ bản cho animate*AsState
được đề cập trong phần trước. Việc sử dụng trực tiếp Animatable
giúp kiểm soát chặt chẽ hơn theo một số cách:
- Trước tiên,
Animatable
có thể có giá trị ban đầu khác với giá trị mục tiêu đầu tiên. Ví dụ: ví dụ về mã trước đó cho thấy một hộp màu xám ở đầu tiên, ngay lập tức tạo ảnh động sang màu xanh lục hoặc đỏ. - Thứ hai,
Animatable
cung cấp nhiều thao tác hơn trên giá trị nội dung, cụ thể làsnapTo
vàanimateDecay
.snapTo
đặt giá trị hiện tại thành giá trị mục tiêu ngay lập tức. Điều này hữu ích khi ảnh động không phải là nguồn đáng tin cậy duy nhất và phải đồng bộ hoá với các trạng thái khác, chẳng hạn như các sự kiện chạm.animateDecay
bắt đầu một ảnh động chậm lại từ tốc độ đã cho. Điều này rất hữu ích trong việc triển khai hành vi vuốt nhanh.
Hãy xem phần Cử chỉ và ảnh động để biết thêm thông tin.
Theo mặc định, Animatable
hỗ trợ Float
và Color
, nhưng bạn có thể dùng bất kỳ loại dữ liệu nào bằng cách cung cấp một TwoWayConverter
. Hãy xem AnimationVector để biết thêm thông tin.
Bạn có thể sử dụng AnimationSpec
để tuỳ chỉnh thông số kỹ thuật của ảnh động.
Hãy xem AnimationSpec
để biết thêm thông tin.
Animation
: Ảnh động được kiểm soát bằng phương pháp thủ công
Animation
là API Ảnh động cấp thấp nhất hiện có. Nhiều ảnh động mà chúng ta thấy cho đến nay đã xây dựng trên Animation
. Có hai kiểu Animation
phụ: TargetBasedAnimation
và DecayAnimation
.
Chỉ dùng Animation
để kiểm soát thời lượng của ảnh động theo cách thủ công. Animation
không có trạng thái và không có bất kỳ khái niệm nào về vòng đời. Nó đóng vai trò như một công cụ tính toán ảnh động cho các API cấp cao hơn.
TargetBasedAnimation
Các API khác dùng được trong hầu hết các trường hợp, nhưng việc sử dụng trực tiếp TargetBasedAnimation
cho phép bạn kiểm soát thời lượng phát ảnh động. Trong ví dụ sau, bạn kiểm soát thời gian phát của TargetAnimation
theo cách thủ công dựa trên khung thời gian do withFrameNanos
cung cấp.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
Không giống như TargetBasedAnimation
, DecayAnimation
không yêu cầu cung cấp targetValue
. Thay vào đó, phương thức này tính toán targetValue
dựa trên các điều kiện bắt đầu do initialVelocity
và initialValue
cung cấp, cũng như DecayAnimationSpec
được cung cấp.
Ảnh động phân rã thường được sử dụng sau cử chỉ hất để làm chậm các phần tử xuống điểm dừng. Tốc độ ảnh động bắt đầu ở giá trị mà initialVelocityVector
thiết lập và sẽ chậm lại theo thời gian.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Tuỳ chỉnh ảnh động
- Ảnh động trong Compose
- Công cụ sửa đổi ảnh động và thành phần kết hợp