Có một số điều mà chúng ta phải cân nhắc khi thao tác với cả với các sự kiện chạm và ảnh động, so với khi chúng ta chỉ thao tác với các ảnh động. Trước hết, chúng ta có thể cần làm gián đoạn ảnh động đang diễn ra khi các sự kiện chạm bắt đầu do tương tác của người dùng sẽ có mức độ ưu tiên cao nhất.
Trong ví dụ bên dưới, chúng tôi sử dụng Animatable
để thể hiện cho vị trí chênh lệch của
thành phần vòng kết nối. Các sự kiện nhấn được xử lý bằng
công cụ sửa đổi
pointerInput
. Khi phát hiện một sự kiện nhấn mới, chúng ta gọi lệnh animateTo
để tạo ảnh động
cho giá trị chênh lệch vào vị trí nhấn. Một sự kiện chạm cũng có thể xảy ra trong quá trình tạo ảnh động và trong trường hợp đó, animateTo
làm gián đoạn ảnh động đang diễn ra và chạy ảnh động đó đến vị trí mục tiêu mới trong khi vẫn duy trì tốc độ của ảnh động bị gián đoạn.
@Composable fun Gesture() { val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { coroutineScope { while (true) { // Detect a tap event and obtain its position. awaitPointerEventScope { val position = awaitFirstDown().position launch { // Animate to the tap position. offset.animateTo(position) } } } } } ) { Circle(modifier = Modifier.offset { offset.value.toIntOffset() }) } } private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt())
Một mẫu thường gặp khác là chúng ta cần đồng bộ hoá các giá trị ảnh động với các giá trị hình thành từ các sự kiện chạm, chẳng hạn như kéo. Trong ví dụ bên dưới, chúng ta thấy tính năng "vuốt để loại bỏ" được triển khai dưới dạng Modifier
(thay vì sử dụng thành phần kết hợp SwipeToDismiss
). Độ chênh lệch chiều ngang của thành phần được biểu thị dưới dạng Animatable
. API này có một đặc điểm hữu ích trong ảnh động cử chỉ. Sự kiện nhấn
cũng như nội dung ảnh động có thể thay đổi giá trị của API. Khi nhận được một
sự kiện nhấn, chúng ta sẽ dừng Animatable
bằng phương thức stop
để
có thể chặn mọi ảnh động đang phát.
Trong một sự kiện kéo, chúng ta sử dụng snapTo
để cập nhật giá trị Animatable
bằng
giá trị được tính từ các sự kiện nhấn. Để vuốt nhanh, công cụ Compose cung cấp
VelocityTracker
để ghi lại các sự kiện kéo và tính tốc độ. Vận tốc có thể được
cung cấp trực tiếp cho animateDecay
để tạo ảnh động vuốt nhanh. Khi muốn trượt
giá trị độ dời trở về vị trí ban đầu, chúng ta chỉ định giá trị chênh lệch
mục tiêu của 0f
bằng phương thức animateTo
.
fun Modifier.swipeToDismiss( onDismissed: () -> Unit ): Modifier = composed { val offsetX = remember { Animatable(0f) } pointerInput(Unit) { // Used to calculate fling decay. val decay = splineBasedDecay<Float>(this) // Use suspend functions for touch events and the Animatable. coroutineScope { while (true) { val velocityTracker = VelocityTracker() // Stop any ongoing animation. offsetX.stop() awaitPointerEventScope { // Detect a touch down event. val pointerId = awaitFirstDown().id horizontalDrag(pointerId) { change -> // Update the animation value with touch events. launch { offsetX.snapTo( offsetX.value + change.positionChange().x ) } velocityTracker.addPosition( change.uptimeMillis, change.position ) } } // No longer receiving touch events. Prepare the animation. val velocity = velocityTracker.calculateVelocity().x val targetOffsetX = decay.calculateTargetValue( offsetX.value, velocity ) // The animation stops when it reaches the bounds. offsetX.updateBounds( lowerBound = -size.width.toFloat(), upperBound = size.width.toFloat() ) launch { if (targetOffsetX.absoluteValue <= size.width) { // Not enough velocity; Slide back. offsetX.animateTo( targetValue = 0f, initialVelocity = velocity ) } else { // The element was swiped away. offsetX.animateDecay(velocity, decay) onDismissed() } } } } } .offset { IntOffset(offsetX.value.roundToInt(), 0) } }
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Ảnh động dựa trên giá trị
- Kéo, vuốt và hất
- Tìm hiểu về cử chỉ