1. Trước khi bắt đầu
Lớp học lập trình này đưa ra hướng dẫn thực tế về những kiến thức cơ bản liên quan đến việc triển khai thao tác kéo và thả cho Compose. Bạn sẽ tìm hiểu cách làm sao để khung hiển thị có thể được kéo và thả cả trong ứng dụng của bạn lẫn trên nhiều ứng dụng. Bạn sẽ tìm hiểu cách triển khai thao tác kéo và thả trong ứng dụng của mình và thậm chí là trên nhiều ứng dụng.
Điều kiện tiên quyết
Để hoàn tất lớp học lập trình này, bạn cần:
- Có kinh nghiệm tạo ứng dụng Android
- Có kinh nghiệm về Jetpack Compose và Đối tượng sửa đổi
Bạn sẽ thực hiện
Tạo một ứng dụng đơn giản có các đặc điểm sau:
- Định cấu hình thành phần kết hợp để có thể kéo thông qua đối tượng sửa đổi dragAndDropSource
- Định cấu hình thành phần kết hợp để làm đích thả thông qua đối tượng sửa đổi dragAndDropTarget
Bạn cần có
- Android Studio Jellyfish trở lên
- Trình mô phỏng hoặc thiết bị Android
2. Sự kiện kéo và thả
Một thao tác kéo và thả có thể được xem là sự kiện gồm 4 giai đoạn. Các giai đoạn đó là:
- Bắt đầu: Hệ thống bắt đầu thao tác kéo và thả để phản hồi cử chỉ kéo của người dùng.
- Tiếp tục: Người dùng tiếp tục kéo.
- Kết thúc: Người dùng thả thành phần kết hợp được kéo vào đích thả
- Tồn tại: Hệ thống gửi tín hiệu để kết thúc thao tác kéo và thả.
Hệ thống sẽ gửi sự kiện kéo trong đối tượng DragEvent
. Đối tượng DragEvent
có thể chứa dữ liệu sau đây
ActionType
: Giá trị cho hành động của sự kiện, dựa trên sự kiện trong vòng đời của sự kiện kéo và thả. Ví dụ:ACTION_DRAG_STARTED
,ACTION_DROP
, v.v.ClipData
: Dữ liệu được kéo, đóng gói trong đối tượng ClipDataClipDescription
: Thông tin tổng quát về đối tượng ClipDataResult
: Kết quả của thao tác kéo và thảX
: Toạ độ x cho vị trí hiện tại của đối tượng được kéoY
: Toạ độ y cho vị trí hiện tại của đối tượng được kéo
3. Thiết lập
Tạo một dự án mới rồi chọn mẫu "Empty Activity" (Chưa có hoạt động):
Giữ nguyên tất cả tham số theo mặc định.
Trong lớp học lập trình này, chúng ta sẽ dùng ImageView để minh hoạ chức năng kéo và thả. Hãy thêm phần phụ thuộc gradle cho thư viện glide để tạo và đồng bộ hoá dự án.
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
Bây giờ, trong MainActivity.kt
, hãy tạo composable
cho Hình ảnh để làm nguồn kéo phục vụ cho mục đích của chúng ta
@Composable
fun DragImage(url: String) {
GlideImage(model = url, contentDescription = "Dragged Image")
}
Tương tự, hãy tạo hình ảnh đích Thả.
@Composable
fun DropTargetImage(url: String) {
val urlState = remember {mutableStateOf(url)}
GlideImage(model = urlState.value, contentDescription = "Dropped Image")
}
Thêm một thành phần kết hợp cột vào thành phần kết hợp của bạn để đưa 2 hình ảnh này vào.
Column {
DragImage(url = getString(R.string.source_url))
DropTargetImage(url = getString(R.string.target_url))
}
Ở giai đoạn này, chúng ta có MainActivity
sẽ hiển thị 2 hình ảnh theo chiều dọc. Bạn sẽ có thể thấy màn hình sau đây.
4. Định cấu hình nguồn kéo
Hãy thêm một đối tượng sửa đổi cho nguồn Kéo và thả của thành phần kết hợp DragImage
modifier = Modifier.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText("image uri", url)
)
)
}
)
}
Ở đây, chúng ta đã thêm một đối tượng sửa đổi dragAndDropSource
. Đối tượng sửa đổi dragAndDropSource
kích hoạt chức năng kéo và thả cho mọi phần tử được áp dụng. Đối tượng này biểu thị trực quan phần tử được kéo dưới dạng bóng khi kéo.
Đối tượng sửa đổi dragAndDropSource
cung cấp PointerInputScope
để phát hiện cử chỉ kéo. Chúng ta đã dùng detectTapGesture
PointerInputScope
để phát hiện cử chỉ kéo là longPress.
onLongPress
là phương thức chúng ta dùng để bắt đầu quá trình chuyển dữ liệu đang được kéo.
startTransfer
bắt đầu một phiên kéo và thả, trong đó transferData là dữ liệu sẽ được chuyển khi hoàn thành cử chỉ. Phương thức này lấy dữ liệu được đóng gói trong DragAndDropTransferData
, gồm 3 trường sau đây
Clipdata:
: dữ liệu thực tế sẽ được chuyểnflags
: cờ để điều khiển thao tác kéo và thảlocalState
: trạng thái cục bộ của phiên khi kéo trong cùng một hoạt động
ClipData
là một đối tượng phức tạp có chứa nhiều loại mục, bao gồm văn bản, mã đánh dấu, âm thanh, video, v.v. Để phục vụ mục đích của lớp học lập trình này, chúng ta sẽ sử dụng imageurl làm một mục trong ClipData.
Tuyệt vời, giờ chúng ta có thể kéo khung hiển thị rồi!
5. Định cấu hình đích thả
Để chấp nhận mục được thả, khung hiển thị phải thêm dragAndDropTarget
modifier
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = {
// condition to accept dragged item
},
target = // DragAndDropTarget
)
)
dragAndDropTarget
là đối tượng sửa đổi cho phép kéo dữ liệu vào thành phần kết hợp. Đối tượng sửa đổi này có 2 tham số
shouldStartDragAndDrop
: Cho phép Thành phần kết hợp quyết định xem có muốn nhận sự kiện từ một phiên kéo và thả cụ thể hay không, bằng cách kiểm tra DragAndDropEvent đã bắt đầu phiên đó.target
: DragAndDropTarget sẽ nhận các sự kiện cho một phiên kéo và thả cụ thể.
Hãy thêm một điều kiện khi muốn truyền một sự kiện kéo đến DragAndDropTarget
.
shouldStartDragAndDrop = { event ->
event.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
}
Ở đây, chúng ta đã thêm điều kiện là chỉ được thả khi ít nhất một trong những mục đang được kéo là văn bản thuần tuý. Đích thả sẽ không được kích hoạt nếu không có mục nào là văn bản thuần tuý.
Đối với các tham số đích, hãy tạo một đối tượng DragAndDropTarget
để xử lý phiên thả.
val dndTarget = remember{
object : DragAndDropTarget{
// handle Drag event
}
}
DragAndDropTarget
có một lệnh gọi lại để ghi đè mọi giai đoạn trong phiên kéo và thả.
onDrop
: Một mục đã được thả vào DragAndDropTarget này, trả về giá trị true nếu DragAndDropEvent được sử dụng; giá trị false nếu sự kiện đó bị từ chốionStarted
: Một phiên kéo và thả vừa bắt đầu và DragAndDropTarget này đủ điều kiện để nhận phiên đó. Điều này tạo cơ hội thiết lập trạng thái cho DragAndDropTarget để chuẩn bị sử dụng một phiên kéo và thả.onEntered
: Một mục đang được thả đã vào phạm vi của DragAndDropTarget này.onMoved
: Một mục đang được thả đã di chuyển trong phạm vi của DragAndDropTarget này.onExited
: Một mục đang được thả đã di chuyển ra ngoài phạm vi của DragAndDropTarget này.onChanged
: Một sự kiện trong phiên kéo và thả hiện tại đã thay đổi trong phạm vi của DragAndDropTarget. Có thể một phím bổ trợ đã được nhấn hoặc nhảonEnded
: Phiên kéo và thả đã hoàn tất. Mọi thực thể DragAndDropTarget trong hệ thống phân cấp trước đó đã nhận được một sự kiện onStarted sẽ nhận được sự kiện này. Điều này tạo cơ hội đặt lại trạng thái cho DragAndDropTarget.
Hãy xác định điều gì sẽ xảy ra khi một mục được thả vào thành phần kết hợp đích.
override fun onDrop(event: DragAndDropEvent): Boolean {
val draggedData = event.toAndroidDragEvent().clipData.getItemAt(0).text
urlState.value = draggedData.toString()
return true
}
Trong hàm onDrop
, chúng ta đang trích xuất mục ClipData
và gán cho URL hình ảnh, đồng thời trả về giá trị true để biểu thị rằng đã xử lý chính xác sự kiện thả.
Chúng ta không cần gán thực thể DragAndDropTarget
này cho tham số đích của đối tượng sửa đổi dragAndDropTarget
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
},
target = dndTarget
)
Tuyệt vời, giờ chúng ta có thể thực hiện thành công thao tác kéo và thả rồi!
Mặc dù chúng ta đã thêm chức năng kéo và thả, nhưng rất khó để hiểu được điều gì đang xảy ra qua hình ảnh. Hãy thay đổi điều đó.
Đối với thành phần kết hợp đích thả, hãy áp dụng ColorFilter
cho hình ảnh
var tintColor by remember {
mutableStateOf(Color(0xffE5E4E2))
}
Sau khi xác định được màu phủ, hãy thêm ColorFilter
vào hình ảnh
GlideImage(
colorFilter = ColorFilter.tint(color = backgroundColor,
blendMode = BlendMode.Modulate),
// other params
)
Chúng ta muốn áp dụng màu phủ cho hình ảnh khi một mục được kéo vào vùng đích Thả. Để làm được điều này, chúng ta có thể ghi đè lệnh gọi lại onEntered
override fun onEntered(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xff00ff00)
}
Ngoài ra, khi người dùng kéo ra khỏi vùng đích, chúng ta nên khôi phục bộ lọc màu gốc. Để làm được điều này, chúng ta phải ghi đè lệnh gọi lại onExited
override fun onExited(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
Sau khi hoàn thành xong thao tác kéo và thả, chúng ta cũng có thể hoàn nguyên về ColorFilter
gốc
override fun onEnded(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
Cuối cùng, thành phần kết hợp thả của chúng ta trông sẽ như sau
@Composable
fun DropTargetImage(url: String) {
val urlState = remember {
mutableStateOf(url)
}
var tintColor by remember {
mutableStateOf(Color(0xffE5E4E2))
}
val dndTarget = remember {
object : DragAndDropTarget {
override fun onDrop(event: DragAndDropEvent): Boolean {
val draggedData = event.toAndroidDragEvent()
.clipData.getItemAt(0).text
urlState.value = draggedData.toString()
return true
}
override fun onEntered(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xff00ff00)
}
override fun onEnded(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
override fun onExited(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
}
}
GlideImage(
model = urlState.value,
contentDescription = "Dropped Image",
colorFilter = ColorFilter.tint(color = tintColor,
blendMode = BlendMode.Modulate),
modifier = Modifier
.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event
.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
},
target = dndTarget
)
)
}
Thật tuyệt, chúng ta có thể thêm các chỉ dấu bằng hình ảnh cho thao tác kéo và thả!
6. Xin chúc mừng!
Compose cho tính năng kéo và thả cung cấp một giao diện dễ dàng triển khai chức năng kéo và thả trong Compose, thông qua các đối tượng sửa đổi của khung hiển thị.
Tóm lại, bạn đã học được cách triển khai thao tác kéo và thả bằng Compose. Hãy khám phá thêm tài liệu.