Tạo ảnh động cho một giá trị duy nhất bằng animate*AsState
Các hàm animate*AsState
là API ảnh động đơn giản nhất trong Compose để tạo ảnh động cho một giá trị duy nhất. Bạn chỉ cung cấp giá trị nhắm mục tiêu (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.
Dưới đây là ví dụ về cách tạo ảnh động alpha bằng API này. Chỉ cần 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 alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Lưu ý rằng 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.
Ngay lập tức, 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ể dễ dàng 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 để đảm bảo loại an toàn, 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, chúng ta 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. Chúng ta có thể sử dụng updateTransition
cùng với MutableTransitionState
để đạt được
điều này. Ví dụ: chúng ta được quyền 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 các nội dung chuyển đổi nhập/thoát cần thiết khi targetState
của Transition
thay đổi. Các hàm mở rộng này cho phép tất cả
các ảnh động enter/exit/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ì thông 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.
Xem AnimatedVisibility và AnimatedContent để biết thêm 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à tuỳ chọn hoàn toà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 đó. Việc triển khai chuyển đổi có thể được trích xuất vào hàm riêng mới. Mẫu này rất hữu ích khi cần phải 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 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
. Ảnh động con có thể được thêm vào bằng animateColor
, animatedFloat
hoặc animatedValue
. Ngoài ra, bạn còn cần chỉ định infiniteRepeatable làm 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 nền tảng của các API ảnh động cấp thấp.
Các hàm animate*AsState
là các API đơn giản nhất để hiển thị thay đổi giá trị tức thì dưới dạng giá trị ảnh động. Hàm này được Animatable
hỗ trợ vì đây là một API dựa vào 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ị đó dựa trên
sự thay đổi về trạng thái. rememberInfiniteTransition
tương tự như vậy, nhưng hàm này tạo
một sự 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à
các ảnh động này có thể được tạo 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 một số khả năng tuỳ chỉnh
dành cho Animation
lại có sẵn thông qua các API cấp cao hơn. Hãy xem
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 thông qua 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ừ nhau, nghĩa là việc thay đổi giá trị luôn diễn ra liên tục và mọi ảnh động đang diễn ra sẽ bị huỷ.
Nhiều tính năng của Animatable
, bao gồm cả animateTo
, được cung cấp dưới dạng hàm tạm ngưng. Nghĩa là các tính năng này cần được chứa 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ên, chúng ta 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 có một ảnh động đang hoạt động khi giá trị bị thay đổi, thì ảnh động sẽ bị huỷ 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.
Đây là triển khai ảnh động sao lưu API animate*AsState
đã đề cập trong phần trước. So với animate*AsState
, việc sử dụng
Animatable
trực tiếp giúp kiểm soát chặt chẽ hơn trên một vài khía cạnh. 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ên cho thấy một hộp màu xám ở đầu tiên, ngay lập tức
bắt đầu tạo ảnh động sang màu xanh lục hoặc đỏ. Thứ hai, Animatable
cung cấp nhiều thao tác 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. Đ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 được đồ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
Cử chỉ và ảnh động để biết thêm thông tin.
Ngay từ đầu, 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.
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 trang Ảnh động. Có hai kiểu Animation
phụ: TargetBasedAnimation
và DecayAnimation
.
Bạn chỉ nên 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 mà các API cấp cao hơn sử dụng.
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 tự kiểm soát thời lượng phát ảnh động. Trong ví dụ bên dưới, thời gian phát của TargetAnimation
được kiểm soát 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ị do 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 {:#customize-animations}
- Ảnh động trong Compose
- Công cụ sửa đổi ảnh động và thành phần kết hợp