1. 事前準備
本程式碼研究室提供實用操作說明,讓您瞭解實作 Compose 拖曳作業的基礎知識。您將瞭解如何在應用程式內和不同應用程式間拖曳檢視區塊,以及如何在應用程式內及甚至不同應用程式間實作拖曳作業。
必要條件
如要完成本程式碼研究室,您需符合以下條件:
- 具備建構 Android 應用程式的經驗
- 具備使用 Jetpack Compose 和修飾符的經驗
執行步驟
建立簡易應用程式,執行以下動作:
- 使用 DragAndDropSource 修飾符,將可組合函式設為可拖曳
- 使用 DragAndDropTarget 修飾符,將可組合函式設為放置目標
軟硬體需求
- Android Studio Jellyfish 以上版本
- Android 裝置或模擬器
2. 拖曳事件
拖曳作業可視為 4 階段事件,分別為以下階段:
- 啟動:系統為因應使用者的拖曳手勢而啟動拖曳作業。
- 繼續:使用者繼續拖曳。
- 結束:使用者在放置目標可組合函式中放開拖曳的項目。
- 存在:系統傳送信號以結束拖曳作業。
系統會透過 DragEvent 物件傳送拖曳事件。DragEvent 物件可包含下列資料:
ActionType:拖曳事件的生命週期事件所對應的事件動作值,例如ACTION_DRAG_STARTED、ACTION_DROP等ClipData:要拖曳的資料,封裝在 ClipData 物件中ClipDescription:ClipData 物件相關中繼資訊Result:拖曳作業的結果X:被拖曳物件目前位置的 x 座標Y:被拖曳物件目前位置的 y 座標
3. 設定
建立新專案並選取「Empty Activity」範本:

請保留所有參數的預設值。
在本程式碼研究室中,我們會使用 ImageView 來示範拖曳功能。請為 Compose 的 Glide 程式庫新增 Gradle 依附元件,並同步處理專案。
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
現在在 MainActivity.kt 中,為 Image 建立 composable,做為拖曳來源:
@Composable
fun DragImage(url: String) {
GlideImage(model = url, contentDescription = "Dragged Image")
}
同樣地,建立 Drop 目標圖片。
@Composable
fun DropTargetImage(url: String) {
val urlState = remember {mutableStateOf(url)}
GlideImage(model = urlState.value, contentDescription = "Dropped Image")
}
在可組合函式中新增 Column 可組合函式,納入這兩張圖片。
Column {
DragImage(url = getString(R.string.source_url))
DropTargetImage(url = getString(R.string.target_url))
}
在這個階段,我們已設定 MainActivity,以垂直方式顯示兩張圖片。您應該可以看到這個畫面。

4. 設定 Drag 來源
我們現在要為 DragImage 可組合函式加入拖曳來源的修飾符:
modifier = Modifier.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText("image uri", url)
)
)
}
)
}
我們在此加入了 dragAndDropSource 修飾符。dragAndDropSource 修飾符可對套用的任何元素啟用拖曳功能,並透過視覺化方式,以拖曳陰影呈現被拖曳的元素。
dragAndDropSource 修飾符可提供 PointerInputScope 來偵測拖曳手勢。我們已使用 detectTapGesture PointerInputScope 偵測 longPress (亦即拖曳手勢)。
onLongPress 方法會啟動被拖曳資料的轉移程序。
startTransfer 會啟動拖曳工作階段,並在手勢完成時,使用 TransferData 做為要轉移的資料。這項程序會採用封裝在 DragAndDropTransferData 中,具有以下 3 個欄位的資料:
Clipdata::要轉移的實際資料flags:控制拖曳作業的旗標localState:在同一活動中拖曳時,工作階段的本機狀態
ClipData 是複雜的物件,內含各種類型的項目,包括文字、標記、音訊、影片等。以本程式碼研究室為例,我們會使用 imageurl 做為 ClipData 中的項目。
很好,現在可以拖曳檢視區塊了!

5. 設定 Drop
如要讓檢視畫面接受放置的項目,應新增 dragAndDropTarget modifier:
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = {
// condition to accept dragged item
},
target = // DragAndDropTarget
)
)
dragAndDropTarget 修飾符可允許在可組合函式中拖曳資料。這個修飾符有兩個參數:
shouldStartDragAndDrop:允許 Composable 檢查啟動工作階段的 DragAndDropEvent,判斷是否要從指定拖曳工作階段接收資料。target:即為 DragAndDropTarget,可接收指定拖曳工作階段的事件。
接下來新增要將拖曳事件傳遞至 DragAndDropTarget 的條件。
shouldStartDragAndDrop = { event ->
event.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
}
此處新增的條件,僅在至少一個拖曳項目為純文字時,才接受放置動作。如果所有項目都不是純文字,就不會啟用放置目標。
我們可以為目標參數建立 DragAndDropTarget 物件,處理放置工作階段。
val dndTarget = remember{
object : DragAndDropTarget{
// handle Drag event
}
}
DragAndDropTarget 為拖曳工作階段中的每個階段,提供要覆寫的回呼。
onDrop:一個項目已放置在這個 DragAndDropTarget 內,傳回 true 表示已取用 DragAndDropEvent,false 表示已遭拒。onStarted:剛才已啟動拖曳工作階段,且這個 DragAndDropTarget 符合接收資格。這讓您有機會設定 DragAndDropTarget 的狀態,準備取用拖曳工作階段。onEntered:要放置的項目已進入這個 DragAndDropTarget 的邊界。onMoved:要放置的項目已在這個 DragAndDropTarget 的邊界內移動。onExited:要放置的項目已移出這個 DragAndDropTarget 的邊界。onChanged:目前拖曳工作階段中的事件在 DragAndDropTarget 邊界內有所變更,可能已按下或放開輔助鍵。onEnded:拖曳工作階段已完成。在先前收到 onStarted 事件的階層中,所有 DragAndDropTarget 例項都會收到這個事件。這時可以重設 DragAndDropTarget 的狀態。
接下來,定義當項目放到目標可組合函式中的處理方式。
override fun onDrop(event: DragAndDropEvent): Boolean {
val draggedData = event.toAndroidDragEvent().clipData.getItemAt(0).text
urlState.value = draggedData.toString()
return true
}
在 onDrop 函式中,我們會擷取 ClipData 項目並指派給圖片網址,同時傳回 true,表示已正確處理放置作業。
我們不必將這個 DragAndDropTarget 例項指派給 dragAndDropTarget 修飾符的目標參數:
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
},
target = dndTarget
)
好極了,現在可以順利執行拖曳作業!

雖然我們新增了拖曳功能,但很難以視覺化方式理解具體情況。我們來調整一下吧!
針對放置目標可組合函式,對圖片套用 ColorFilter:
var tintColor by remember {
mutableStateOf(Color(0xffE5E4E2))
}
定義色調顏色後,在圖片中加入 ColorFilter:
GlideImage(
colorFilter = ColorFilter.tint(color = backgroundColor,
blendMode = BlendMode.Modulate),
// other params
)
我們希望在拖曳項目進入 Drop 目標區域時,將色調顏色套用至圖片。我們可以藉由覆寫 onEntered 回呼達到這個效果。
override fun onEntered(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xff00ff00)
}
此外,當使用者拖曳到目標區域以外時,畫面應該回復為原本的色彩濾鏡。為此,我們必須覆寫 onExited 回呼:
override fun onExited(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
順利完成拖曳程序後,也可以還原為原本的 ColorFilter:
override fun onEnded(event: DragAndDropEvent) {
super.onEntered(event)
tintColor = Color(0xffE5E4E2)
}
最後,放置可組合函式大致如下:
@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
)
)
}
好棒,我們可以為拖曳作業加上視覺提示了!

6. 恭喜!
Compose for Drag and Drop 提供簡單的介面,方便您使用檢視區塊專用的修飾符,在 Compose 中實作拖曳功能。
總而言之,您已學會如何使用 Compose 實作拖曳功能,歡迎進一步瀏覽說明文件。