Android Drag and Drop Tutorial: Moving Views and Data
Learn how to use Android’s drag-and-drop framework to enable an advanced gesture-based user experience. By Kushal Kumar R.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Drag and Drop Tutorial: Moving Views and Data
25 mins
- Getting Started
- The Drag-and-Drop Process
- The Four Drag-and-Drop States
- Designing a Drag Operation
- Adding a Drag Shadow
- Customizing a Drag Shadow
- Implementing a Drag Event Listener
- Dispatching Drag Events
- Starting a Drag Operation
- Responding to Drag Events
- Handling Events During the Drag
- Handling a Drop Operation
- Responding to Drag End Events
- Setting the View Visibility
- Moving the Draggable View to a New Position
- Retrieving the Drop's X and Y Position
- Updating the Draggable View's Position
- Indicating Whether the Mask Is on the Face
- Where to Go From Here?
Handling Events During the Drag
Right now, there's no visual indication to show where the user can or can't drop the mask. Your next step will be to handle events during the drag process to change the drop area view depending on where the mask is.
Add the following code snippets to your maskDragListener.
DragEvent.ACTION_DRAG_ENTERED -> {
binding.maskDropArea.alpha = 0.3f
true
}
The code above dims the drop area view when the mask enters the drop area bounds. This indicates to the user that the mask is within the drop area.
DragEvent.ACTION_DRAG_EXITED -> {
binding.maskDropArea.alpha = 1.0f
draggableItem.visibility = View.VISIBLE
view.invalidate()
true
}
This code resets the drop area view opacity to 1.0f
and resets the visibility of the mask view to VISIBLE
when it exits the drop area bounds. This visually indicates that dragging outside the bounds after dropping doesn't work.
Now, you're ready to let the user drop the mask.
Handling a Drop Operation
When the user releases the mask drag shadow, the system needs to dispatch a drag event with the action type ACTION_DROP
to the View with the listener. To implement this, add the following code snippet to your maskDragListener.
DragEvent.ACTION_DROP -> {
//1
binding.maskDropArea.alpha = 1.0f
//2
if (dragEvent.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
val draggedData = dragEvent.clipData.getItemAt(0).text
//TODO : perform any action on the draggedData
}
//3
true
}
The code above performs the following actions on the drag event listener:
- Resets the drop area view opacity to
1.0f
when the user drops the mask. - Optionally reads the data from
ClipData
viagetClipData()
. - After the processing completes successfully, return a Boolean
true
or a Booleanfalse
.ACTION_DRAG_ENDED
will return this value when you callgetResult()
.
The user can release the drag shadow on any view, but the system dispatches a drag event only if the drop area has an active drag event listener indicating that it's ready to accept the drop.
Responding to Drag End Events
When the user drops the drag shadow, the system dispatches a drag event to all the registered drag event listeners with an action type of ACTION_DRAG_ENDED
.
Receiving ACTION_DRAG_ENDED
marks the end of the drag operation.
Setting the View Visibility
Right after dispatching ACTION_DROP
, the system needs to dispatch ACTION_DRAG_ENDED
. Add the following code snippet to your maskDragListener to set the visibility of the mask view:
DragEvent.ACTION_DRAG_ENDED -> {
draggableItem.visibility = View.VISIBLE
view.invalidate()
true
}
When the drag starts, you set the visibility of the mask view to invisible. When the mask is in its new position, you need to redraw the mask. That's what the code above does.
Next, you'll see how to move the object to its new position when the user drops it.
Moving the Draggable View to a New Position
When the user drops the mask's draggable view onto the drop area, you need to position the mask in the drop location. That's your next goal.
Retrieving the Drop's X and Y Position
You'll start by getting the X and Y position of the drag shadow release location from the drag event. This lets you position the mask in the drop area. To do this, call the following code from within the drag event listener implementation for the ACTION_DROP
event:
dragEvent.x
dragEvent.y
To retrieve the x, y coordinates from the drag event object, use the getX()
and getY()
getter methods. The x, y coordinates from the drag event match the last position of the mask drag shadow before the user dropped it.
Updating the Draggable View's Position
In Android, every view on the canvas starts calculating its position from the left-top corner and ends at the bottom-right corner.
If you use the x, y coordinates from the drag event to position the mask, it will anchor to the left-top corner coordinates.
Instead, you want to anchor the center of the mask to the x, y coordinates of the last touch point location. To achieve this, you need to make the following changes before updating the mask's new position.
Create a reference to the draggable mask view: val draggableItem = dragEvent.localState as View
.
To update the draggable mask view's x, y coordinates, first import androidx.constraintlayout.widget.ConstraintLayout
. Then, add the following code snippet to your maskDragListener
's DragEvent.ACTION_DROP
branch:
//1
draggableItem.x = dragEvent.x - (draggableItem.width / 2)
//2
draggableItem.y = dragEvent.y - (draggableItem.height / 2)
//3
val parent = draggableItem.parent as ConstraintLayout
//4
parent.removeView(draggableItem)
//5
val dropArea = view as ConstraintLayout
//6
dropArea.addView(draggableItem)
//7
true
This code aligns the center of the mask with the last touch point before the drop. It also removes the mask from its previous location and adds it to the new location. Going through it step by step, you:
- Reposition the mask with the updated x coordinate. Subtract half the width of the mask view from the drag event's x coordinate. Then, assign the difference in value to the x coordinate of
draggableItem
. - Reposition the mask with the updated y coordinate. Subtract half the height of the mask view from the drag event's y coordinate. Assign the difference in value to the y coordinate of
draggableItem
. - Take a reference to the mask's parent
viewGroup
. - Remove the mask from the parent
viewGroup
. - Take a reference to the new
viewGroup
, the mask drop area. - Add the mask view to this new
viewGroup
. - Return a Boolean
true
to indicate the drop operation succeeded.
At this point, the drag-and-drop operation works, but you have one final improvement to make.
Indicating Whether the Mask Is on the Face
To spice things up, your next step is to let your app indicate whether the mask is on or off the face.
Invoke a new method, checkIfMaskIsOnFace(dragEvent: DragEvent)
, in the maskDragListener
's DragEvent.ACTION_DROP
branch, just before returning true
:
DragEvent.ACTION_DROP -> {
...
checkIfMaskIsOnFace(dragEvent)
true
}
In checkIfMaskIsOnFace()
you will create a reference to the face view, the Bugdroid mascot, to measure the bounds of the view. If the drag event's x and y coordinates are within the bounds of the face view, then the mask is on the face.
Next, add the following variables in MainActivity.kt:
private val maskOn = "Bingo! Mask On"
private val maskOff = "Mask off"
These will serve as the text values for the toast message that checkIfMaskIsOnFace()
will make.
Now it's time to create checkIfMaskIsOnFace(dragEvent: DragEvent)
. Start by importing android.widget.Toast
.
Implement checkIfMaskIsOnFace(dragEvent: DragEvent)
in MainActivity.kt to display the appropriate toast message:
private fun checkIfMaskIsOnFace(dragEvent: DragEvent) {
//1
val faceXStart = binding.faceArea.x
val faceYStart = binding.faceArea.y
//2
val faceXEnd = faceXStart + binding.faceArea.width
val faceYEnd = faceYStart + binding.faceArea.height
//3
val toastMsg = if (dragEvent.x in faceXStart..faceXEnd && dragEvent.y in faceYStart..faceYEnd){
maskOn
} else {
maskOff
}
//4
Toast.makeText(this, toastMsg, Toast.LENGTH_SHORT).show()
}
Here's what the code above does:
- Defines the x, y coordinates of the left-top point of the Bugdroid mascot.
- Adds the face's width and height to the left-top point of the face to calculate the bottom end point's x, y coordinates.
- Checks whether the mask's drop location is within the face bounds. This lets you set an appropriate toast message.
- Displays a toast message indicating whether the mask is on the face or not.
Build and run. You'll see the toast message telling you whether the mask is on or off the face.
Congratulations! You've now completed the Masky app and learned how to use drag and drop in your Android apps.