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 實作拖曳功能,歡迎進一步瀏覽說明文件。