1. 시작하기 전에
이 Codelab에서는 Compose에서 드래그 앤 드롭 작업을 구현하는 기본사항에 관한 실용적인 안내를 제공합니다. 앱 내에서 그리고 여러 앱 간에 뷰를 드래그 앤 드롭하는 방법을 알아보고 앱 내에서 그리고 여러 앱 간에 드래그 앤 드롭 작업을 구현하는 방법도 살펴봅니다.
기본 요건
이 Codelab을 완료하려면 다음이 필요합니다.
- Android 앱 빌드 경험
- Jetpack Compose 및 수정자 사용 경험
실행할 작업
다음을 실행하는 간단한 앱을 만듭니다.
- dragAndDropSource 수정자를 사용하여 드래그 가능하도록 컴포저블 구성
- dragAndDropTarget 수정자를 사용하여 드롭 타겟이 되도록 컴포저블 구성
- Compose를 사용하여 리치 콘텐츠 받기
필요한 항목
- Android 스튜디오 Jellyfish 이상
- Android 기기 또는 에뮬레이터
2. 드래그 앤 드롭 이벤트
드래그 앤 드롭 작업은 4단계의 이벤트로 볼 수 있으며 단계는 다음과 같습니다.
- 시작됨: 시스템이 사용자의 드래그 동작에 응답하여 드래그 앤 드롭 작업을 시작합니다.
- 진행 중: 사용자가 계속 드래그합니다.
- 종료됨: 사용자가 드롭 타겟 컴포저블에서 드래그를 해제합니다.
- 존재함: 시스템이 드래그 앤 드롭 작업을 종료하라는 신호를 보냅니다.
시스템은 DragEvent
객체에서 드래그 이벤트를 전송합니다. DragEvent
객체에는 다음 데이터가 포함될 수 있습니다.
ActionType
: 드래그 앤 드롭 이벤트의 수명 주기 이벤트에 기반한 이벤트의 작업 값입니다. 예:ACTION_DRAG_STARTED
,ACTION_DROP
등ClipData
: 드래그되고 ClipData 객체에 캡슐화되는 데이터입니다.ClipDescription
: ClipData 객체에 관한 메타 정보입니다.Result
: 드래그 앤 드롭 작업의 결과입니다.X
: 드래그된 객체의 현재 위치의 x 좌표입니다.Y
: 드래그된 객체의 현재 위치의 y 좌표입니다.
3. 설정
새 프로젝트를 만들고 'Empty Activity' 템플릿을 선택합니다.
모든 매개변수를 기본값으로 둡니다.
이 Codelab에서는 ImageView를 사용하여 드래그 앤 드롭 기능을 보여줍니다. Compose를 위한 glide 라이브러리의 Gradle 종속 항목을 추가하고 프로젝트를 동기화해 보겠습니다.
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
이제 MainActivity.kt
에서 목적에 맞게 드래그 소스 역할을 할 이미지 composable
을 만듭니다.
@Composable
fun DragImage(url: String) {
GlideImage(model = url, contentDescription = "Dragged Image")
}
마찬가지로 드롭 타겟 이미지를 만듭니다.
@Composable
fun DropTargetImage(url: String) {
val urlState = remember {mutableStateOf(url)}
GlideImage(model = urlState.value, contentDescription = "Dropped Image")
}
컴포저블에 열 컴포저블을 추가하여 이 두 이미지를 포함합니다.
Column {
DragImage(url = getString(R.string.source_url))
DropTargetImage(url = getString(R.string.target_url))
}
이 단계에는 두 개의 이미지를 세로로 표시하는 MainActivity
가 있습니다. 다음 화면이 표시되어야 합니다.
4. 드래그 소스 구성
DragImage 컴포저블의 드래그 앤 드롭 소스 수정자를 추가해 보겠습니다.
modifier = Modifier.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText("image uri", url)
)
)
}
)
}
여기서는 dragAndDropSource
수정자를 추가했습니다. dragAndDropSource
수정자는 적용되는 모든 요소의 드래그 앤 드롭 기능을 사용 설정합니다. 드래그된 요소를 드래그 섀도우로 시각적으로 나타냅니다.
dragAndDropSource
수정자는 드래그 동작을 감지하는 PointerInputScope
를 제공합니다. 드래그 동작인 길게 누르기를 감지하기 위해 detectTapGesture
PointerInputScope
를 사용했습니다.
onLongPress
메서드에서, 드래그되는 데이터의 전송을 시작합니다.
startTransfer
는 동작 완료 시 전송될 데이터로 transferData를 사용하여 드래그 앤 드롭 세션을 시작합니다. 필드 3개가 있는 DragAndDropTransferData
에 캡슐화된 데이터를 사용합니다.
Clipdata:
전송될 실제 데이터입니다.flags
: 드래그 앤 드롭 작업을 제어하는 플래그입니다.localState
: 동일한 활동에서 드래그할 때 세션의 로컬 상태입니다.
ClipData
는 텍스트, 마크업, 오디오, 동영상 등 다양한 유형의 항목을 포함하는 복잡한 객체입니다. 이 Codelab에서는 imageurl을 ClipData의 항목으로 사용합니다.
좋습니다. 이제 뷰를 드래그할 수 있습니다.
5. 드롭 구성
뷰에서 드롭된 항목을 허용하려면 dragAndDropTarget
modifier
를 추가해야 합니다.
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = {
// condition to accept dragged item
},
target = // DragAndDropTarget
)
)
dragAndDropTarget
은 컴포저블에서 데이터를 드래그할 수 있도록 하는 수정자입니다. 이 수정자에는 두 매개변수가 있습니다.
shouldStartDragAndDrop
: 컴포저블이 세션을 시작한 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 내에서 항목이 드롭되었습니다. DragAndDropEvent가 사용되었음을 나타내기 위해 true를 반환합니다. 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
항목을 추출하여 이미지 URL에 할당하고, 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
)
드래그한 항목이 드롭 타겟 영역에 들어갈 때 이미지의 색상에 색조를 적용하려고 합니다. 이를 위해 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는 뷰의 수정자를 사용하여 Compose에서 드래그 앤 드롭 기능을 구현하는 간편한 인터페이스를 제공합니다.
요약에서는 Compose를 사용하여 드래그 앤 드롭을 구현하는 방법을 알아봤습니다. 문서를 자세히 살펴보세요.