Android Drag and Drop tutorial

Android Drag and Drop tutorial

Theory

The Android System provides Drag and Drop framework, which facilitates users especially to move data from one View to another within the current layout using a graphical drag and drop gesture. As of API 11 drag and drop of view onto other views or view groups is supported. The framework includes a drag event class, drag listeners, and helper methods and classes.

Key terms:

  • Drag shadow. During a drag and drop operation, the system displays a image that the user drags. The image is called a drag shadow. You create it with methods you declare for a View.DragShadowBuilder object, and then pass it to the system when you start a drag using startDrag() (startDragAndDrop for API 24).
  • ClipData. For case of data movement in drag and drop, we use ClipData to represent it. It is passed in startDrag() (startDragAndDrop for API 24) and contains description about data being dragged.

When you start a drag, you include both the data you are moving and metadata describing this data as part of the call to the system. During the drag, the system sends drag events to the drag event listeners or callback methods of each View in the layout. The listeners or callback methods can use the metadata to decide if they want to accept the data when it is dropped.

Your application tells the system to start a drag by calling the startDrag() (startDragAndDrop for API 24) method. This tells the system to start sending drag events. The method also sends the data that you are dragging.

There are basically four steps or states in the drag and drop process:

  • Started. This event occurs when you start dragging an item in a layout, your application calls startDrag() (startDragAndDrop for API 24) method to tell the system to start a drag. The arguments inside startDrag() (startDragAndDrop for API 24) method provide the data to be dragged, metadata for this data, and a callback for drawing the drag shadow. The system first responds by calling back to your application to get a drag shadow. It then displays the drag shadow on the device. Next, the system sends a drag event with action type ACTION_DRAG_STARTED to the registered drag event listeners for all the View objects in the current layout. To continue to receive drag events, including a possible drop event, a drag event listener must return true, If the drag event listener returns false, then it will not receive drag events for the current operation until the system sends a drag event with action type ACTION_DRAG_ENDED.
  • Continuing. The user continues the drag. System sends ACTION_DRAG_ENTERED action followed by ACTION_DRAG_LOCATION action to the registered drag event listener for the View where dragging point enters. The listener may choose to alter its View object's appearance in response to the event or can react by highlighting its View. The drag event listener receives a ACTION_DRAG_EXITED action after the user has moved the drag shadow outside the bounding box of the View.
  • Dropped. The user releases the dragged item within the bounding box of a View. The system sends the View object's listener a drag event with action type ACTION_DROP.
  • Ended. Just after the action type ACTION_DROP, the system sends out a drag event with action type ACTION_DRAG_ENDED to indicate that the drag operation is over.

The DragEvent class represents an event that is sent out by the system at various times during a drag and drop operation. This class provides few constants and important methods which we use during Drag/Drop process.

Following are all constants integers available as a part of DragEvent class.

  • ACTION_DRAG_STARTED. Signals the start of a drag and drop operation.
  • ACTION_DRAG_ENTERED. Signals to a View that the drag point has entered the bounding box of the View.
  • ACTION_DRAG_LOCATION. Sent to a View after ACTION_DRAG_ENTERED if the drag shadow is still within the View object's bounding box.
  • ACTION_DRAG_EXITED. Signals that the user has moved the drag shadow outside the bounding box of the View.
  • ACTION_DROP. Signals to a View that the user has released the drag shadow, and the drag point is within the bounding box of the View.
  • ACTION_DRAG_ENDED. Signals to a View that the drag and drop operation has concluded.

Following are few important and most frequently used methods available as a part of DragEvent class.

  • getAction(). Inspect the action value of this event.
  • getClipData(). Returns the ClipData object sent to the system as part of the call to startDrag() (startDragAndDrop for API 24).
  • getClipDescription(). Returns the ClipDescription object contained in the ClipData.
  • getResult(). Returns an indication of the result of the drag and drop operation.
  • getX(). Gets the X coordinate of the drag point.
  • getY(). Gets the Y coordinate of the drag point.
  • toString() Returns a string representation of this DragEvent object.

Listening for Drag Event. If you want any of your views within a Layout should respond Drag event then your View either implements View.OnDragListener or setup onDragEvent(DragEvent) callback method. When the system calls the method or listener, it passes to them a DragEvent object explained above. You can have both a listener and a callback method for View object. If this occurs, the system first calls the listener and then defined callback as long as listener returns true.

The combination of the onDragEvent(DragEvent) method and View.OnDragListener is analogous to the combination of the onTouchEvent() and View.OnTouchListener used with touch events in old versions of Android.

Starting a Drag Event. You start with creating a ClipData and ClipData.Item for the data being moved. As part of the ClipData object, supply metadata that is stored in a ClipDescription object within the ClipData. For a drag and drop operation that does not represent data movement, you may want to use null instead of an actual object.

Next either you can extend extend View.DragShadowBuilder to create a drag shadow for dragging the view or simply you can use View.DragShadowBuilder(View) to create a default drag shadow that's the same size as the View argument passed to it, with the touch point centered in the drag shadow.

Example

This example shows a simple drag and drop functionality, in which an item can be moved from one View to another.

First, we should make some drawables in order to distinguish the different views in the layout, when we drag and drop an item.

Open res/drawable/box1.xml and paste the following code.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@android:color/holo_blue_light"/>
    <corners android:radius="5dp"/>
</shape>

Open res/drawable/box2.xml and paste the following code.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@android:color/holo_green_light"/>
    <corners android:radius="5dp"/>
</shape>

Open res/drawable/box3.xml and paste the following code.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="@android:color/holo_red_light"/>
    <corners android:radius="5dp"/>
</shape>

In main activity layout, res/layout/activity_main.xml, define two LinearLayout to act as left and right target view. Initially, in our layout all three ImageView boxes will be present in left view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <LinearLayout
        android:id="@+id/leftContainer"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:background="#E8E6E7"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/box1"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_gravity="center_vertical|center_horizontal"
            android:layout_margin="10dp"
            android:background="@drawable/box1" />

        <ImageView
            android:id="@+id/box2"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_gravity="center_vertical|center_horizontal"
            android:layout_margin="10dp"
            android:background="@drawable/box2" />

        <ImageView
            android:id="@+id/box3"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_gravity="center_vertical|center_horizontal"
            android:layout_margin="10dp"
            android:background="@drawable/box3" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/rightContainer"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:background="#B1BEC4"
        android:gravity="center_vertical"
        android:orientation="vertical">
    </LinearLayout>

</LinearLayout>

A drag and drop event occurs when a user starts dragging data, so the application tells the system to start a drag by calling startDrag() (startDragAndDrop for API 24) method. After that, the system calls back the application, in order to take the drag shadow, and it sends a DragEvent. If we want every View object in the layout to respond to the sending drag event, we should register a listener (onDragListener or onTouchListener) for each one or setup a callback method (onDrag or onTouch respectively) and define other views as possible drop targets. If we use both listener and callback method for the View, the system calls the listener first and then the callback, as long as the listener still handles successfully the drag event.

In our example, we will implement the OnTouchListener OnDragListener interfaces.

Open MainActivity.java file and paste the following code.

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnDragListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.box1).setOnTouchListener(this);
        findViewById(R.id.box2).setOnTouchListener(this);
        findViewById(R.id.box3).setOnTouchListener(this);

        findViewById(R.id.leftContainer).setOnDragListener(this);
        findViewById(R.id.rightContainer).setOnDragListener(this);
    }

    @Override
    @SuppressWarnings("deprecation")
    public boolean onTouch(View view, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);

            ClipData data = ClipData.newPlainText("id", view.getResources().getResourceEntryName(view.getId()));

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                view.startDragAndDrop(data, shadowBuilder, view, 0);
            } else {
                view.startDrag(data, shadowBuilder, view, 0);
            }

            view.setVisibility(View.INVISIBLE);
            return true;
        }
        return false;
    }

    @Override
    public boolean onDrag(View v, DragEvent event) {
        switch (event.getAction()) {
            // signal for the start of a drag and drop operation
            case DragEvent.ACTION_DRAG_STARTED:
                // do nothing
                break;

            // the drag point has entered the bounding box of the View
            case DragEvent.ACTION_DRAG_ENTERED:
                v.setBackgroundColor(0xFFFFF6F9);
                break;

            // the user has moved the drag shadow outside the bounding box of the View
            case DragEvent.ACTION_DRAG_EXITED:
                v.setBackgroundColor(v.getId() == R.id.leftContainer ? 0xFFE8E6E7 : 0xFFB1BEC4);
                break;

            // the drag and drop operation has concluded
            case DragEvent.ACTION_DRAG_ENDED:
                v.setBackgroundColor(v.getId() == R.id.leftContainer ? 0xFFE8E6E7 : 0xFFB1BEC4);
                break;

            //drag shadow has been released,the drag point is within the bounding box of the View
            case DragEvent.ACTION_DROP:
                View view = (View) event.getLocalState();
                // we want to make sure it is dropped only to left and right parent view
                if (v.getId() == R.id.leftContainer || v.getId() == R.id.rightContainer) {

                    ViewGroup source = (ViewGroup) view.getParent();
                    source.removeView(view);

                    LinearLayout target = (LinearLayout) v;
                    target.addView(view);

                    String id = event.getClipData().getItemAt(0).getText().toString();
                    Toast.makeText(this, id + " dropped!", Toast.LENGTH_SHORT).show();
                }
                // make view visible as we set visibility to invisible while starting drag
                view.setVisibility(View.VISIBLE);
                break;
        }

        return true;
    }
}

As you can notice in the code above, for the dragging data you can use the ClipData or the ClipData.Item, which contains data that is dropped to the target-drop views. The ClipData object includes ClipDescription, which stores important meta-data about the clip. In addition, DragShadowBuilder is used in order to display an image during the drag and drop operation.

android_drag_and_drop_example.png
comments powered by Disqus