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.

4.8 (4) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

  1. Resets the drop area view opacity to 1.0f when the user drops the mask.
  2. Optionally reads the data from ClipData via getClipData().
  3. After the processing completes successfully, return a Boolean true or a Boolean false. ACTION_DRAG_ENDED will return this value when you call getResult().

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:

  1. 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.
  2. 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.
  3. Take a reference to the mask's parent viewGroup.
  4. Remove the mask from the parent viewGroup.
  5. Take a reference to the new viewGroup, the mask drop area.
  6. Add the mask view to this new viewGroup.
  7. 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:

  1. Defines the x, y coordinates of the left-top point of the Bugdroid mascot.
  2. 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.
  3. Checks whether the mask's drop location is within the face bounds. This lets you set an appropriate toast message.
  4. 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.

Android Drag and Drop Mask

Congratulations! You've now completed the Masky app and learned how to use drag and drop in your Android apps.