Android 3.0 Honeycomb Drag and Drop tutorial


Since Android 3.0 Honeycomb we have drag and drop framework which allows us to move data from one view to another in current layout by simple graphical drag and drop gesture.

The Drag and Drop Framework has ClipData, DragEvent & DragShadowBuilder classes introduced along with couple of helper methods which allows to achieve drag and drop feature.

The drag & drop operation begins when user makes some kind of gesture on screen which indicates the start of drag. For example, user can perform long press on item before he starts dragging it. (Just like it happens on home screen)

When the drag event starts, systems call backs the application to get the representation of data being dragged.
Developer can create this representation be extending DragShadowBuilder class. Developer can also ignore the implementation of DragShadowBuilder and rely on system’s default representation of data (which will be the view itself).

While the view is getting dragged on screen, system generates DragEvents which can be intercepted by application by setting View.setOnDragListener().

OnDragListener interface has callback method public boolean onDrag(View view, DragEvent dragEvent)
which will get called whenever any view is dragged or dropped.

The DragEvent parameter provides getAction() which tells the application which type of action has occurred.

Following types of action can be identified.

DragEvent.ACTION_DRAG_STARTED – Drag event has started
DragEvent.ACTION_DRAG_ENTERED – Drag has brought the drop shadow in view bounds.
DragEvent.ACTION_DRAG_EXITED – Drag has taken the drop shadow out of view bounds.
DragEvent.ACTION_DRAG_LOCATION – Drag is happening and drop shadow is inside view bounds.
DragEvent.ACTION_DROP – Drop has happened inside view bounds.
DragEvent.ACTION_DRAG_ENDED – Drop has happened outside view bounds.

Now lets have a look at a sample code and see how it looks.

To demonstrate the drag and drop, I am going to create a simple grid view and a trash can(Image view). User can drag an item from grid view to trash can to delete that item.

Lets have a look at layout file

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parent_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <GridView
        android:id="@+id/grid_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:horizontalSpacing="10dip"
        android:numColumns="4"
        android:verticalSpacing="10dip" />

    <ImageView
        android:id="@+id/trash_can"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:contentDescription="Delete"
        android:padding="40dip"
        android:src="@drawable/trash_can"
        android:visibility="gone" >
    </ImageView>

</RelativeLayout>

 
Lets fill the child views in grid.

ArrayList drawables;

private BaseAdapter adapter;
private int draggedIndex = -1;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    drawables = new ArrayList();
    drawables.add(R.drawable.sample_0);
    drawables.add(R.drawable.sample_1);
    drawables.add(R.drawable.sample_2);
    drawables.add(R.drawable.sample_3);
    drawables.add(R.drawable.sample_4);
    drawables.add(R.drawable.sample_5); 
    drawables.add(R.drawable.sample_6);
    drawables.add(R.drawable.sample_7);
    GridView gridView = (GridView) findViewById(R.id.grid_view);
        gridView.setOnItemLongClickListener(MainActivity.this);
    gridView.setAdapter(adapter = new BaseAdapter() {

        @Override
        // Get a View that displays the data at the specified position in
        // the data set.
        public View getView(int position, View convertView,
                ViewGroup gridView) {
            // try to reuse the views.
            ImageView view = (ImageView) convertView;
            // if convert view is null then create a new instance else reuse
            // it
            if (view == null) {
                view = new ImageView(MainActivity.this);
            }
            view.setImageResource(drawables.get(position));
            view.setTag(String.valueOf(position));
            return view;
        }
    
        @Override
        // Get the row id associated with the specified position in the
        // list.
        public long getItemId(int position) {
            return position;
        }

        @Override
        // Get the data item associated with the specified position in the
        // data set.
        public Object getItem(int position) {
            return drawables.get(position);
        }

        @Override
        // How many items are in the data set represented by this Adapter.
        public int getCount() {
            return drawables.size();
        }
    });
}

 

You can see that I’ve set the activity as onItemLongCLickListener. Lets look at on onItemLongClick method

We want to start the drag operation when an item is long clicked, we also want to make the trash can visible.
To achieve this we will have to create ClipData.Item and then we will create ClipData object which be used to start drag.

Since we want both, dragged view and trash can to get the drag events, we will set drag listeners for both of them.

@Override
    public boolean onItemLongClick(AdapterView gridView, View view,
            int position, long row) {
        ClipData.Item item = new ClipData.Item((String) view.getTag());
        ClipData clipData = new ClipData((CharSequence) view.getTag(),
                new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
        view.startDrag(clipData, new View.DragShadowBuilder(view), null, 0);
        View trashCan = findViewById(R.id.trash_can);
        trashCan.setVisibility(View.VISIBLE);
        trashCan.setOnDragListener(MainActivity.this);

        trashCan.setOnDragListener(MainActivity.this);
        draggedIndex = position;
        return true;
    }

 

Now looks like my activity is playing lot many roles. It is also a drag listener. Lets look at how my onDrag method looks like.

@Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        switch (dragEvent.getAction()) {
        case DragEvent.ACTION_DRAG_STARTED:
            // Drag has started
            // If called for trash resize the view and return true
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
                return true;
            } else // else check the mime type and set the view visibility
            if (dragEvent.getClipDescription().hasMimeType(
                    ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                view.setVisibility(View.GONE);
                return true;

            } else {
                return false;
            }
        case DragEvent.ACTION_DRAG_ENTERED:
            // Drag has entered view bounds
            // If called for trash can then scale it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.5f);
                view.animate().scaleY(1.5f);
            }
            return true;
        case DragEvent.ACTION_DRAG_EXITED:
            // Drag exited view bounds
            // If called for trash can then reset it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            }
            view.invalidate();
            return true;
        case DragEvent.ACTION_DRAG_LOCATION:
            // Ignore this event
            return true;
        case DragEvent.ACTION_DROP:
            // Dropped inside view bounds
            // If called for trash can then delete the item and reload the grid
            // view
            if (view.getId() == R.id.trash_can) {
                drawables.remove(draggedIndex);
                draggedIndex = -1;
            }
            adapter.notifyDataSetChanged();
        case DragEvent.ACTION_DRAG_ENDED:
            // Hide the trash can
            new Handler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    findViewById(R.id.trash_can).setVisibility(View.GONE);
                }
            }, 1000l);
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            } else {
                view.setVisibility(View.VISIBLE);
            }
            // remove drag listeners
            view.setOnDragListener(null);
            return true;

        }
        return false;
    }

 
I think code is self explanatory, this method is called twice for every event and code takes care of identifying for which view it is called for and takes the action accordingly.

The only thing to notice here is that if we return true for DragEvent.ACTION_DRAG_STARTED then only we will get further events of DragEvent.ACTION_DRAG_ENTERED & DragEvent.ACTION_DRAG_EXITED. Otherwise these events will be ignored by system.

Lets put all this together and see how it looks.

/**
 * @author Chitranshu Asthana
 */

package com.example.dragviewexample;

import java.util.ArrayList;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.Bundle;
import android.os.Handler;
import android.view.DragEvent;
import android.view.Menu;
import android.view.View;
import android.view.View.OnDragListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnDragListener,
        OnItemLongClickListener {

    ArrayList drawables;

    private BaseAdapter adapter;
    private int draggedIndex = -1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        drawables = new ArrayList();
        drawables.add(R.drawable.sample_0);
        drawables.add(R.drawable.sample_1);
        drawables.add(R.drawable.sample_2);
        drawables.add(R.drawable.sample_3);
        drawables.add(R.drawable.sample_4);
        drawables.add(R.drawable.sample_5);
        drawables.add(R.drawable.sample_6);
        drawables.add(R.drawable.sample_7);
        GridView gridView = (GridView) findViewById(R.id.grid_view);
        gridView.setOnItemLongClickListener(MainActivity.this);
        gridView.setAdapter(adapter = new BaseAdapter() {

            @Override
            // Get a View that displays the data at the specified position in
            // the data set.
            public View getView(int position, View convertView,
                    ViewGroup gridView) {
                // try to reuse the views.
                ImageView view = (ImageView) convertView;
                // if convert view is null then create a new instance else reuse
                // it
                if (view == null) {
                    view = new ImageView(MainActivity.this);
                }
                view.setImageResource(drawables.get(position));
                view.setTag(String.valueOf(position));
                return view;
            }

            @Override
            // Get the row id associated with the specified position in the
            // list.
            public long getItemId(int position) {
                return position;
            }

            @Override
            // Get the data item associated with the specified position in the
            // data set.
            public Object getItem(int position) {
                return drawables.get(position);
            }

            @Override
            // How many items are in the data set represented by this Adapter.
            public int getCount() {
                return drawables.size();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        switch (dragEvent.getAction()) {
        case DragEvent.ACTION_DRAG_STARTED:
            // Drag has started
            // If called for trash resize the view and return true
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
                return true;
            } else // else check the mime type and set the view visibility
            if (dragEvent.getClipDescription().hasMimeType(
                    ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                view.setVisibility(View.GONE);
                return true;

            } else {
                return false;
            }
        case DragEvent.ACTION_DRAG_ENTERED:
            // Drag has entered view bounds
            // If called for trash can then scale it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.5f);
                view.animate().scaleY(1.5f);
            }
            return true;
        case DragEvent.ACTION_DRAG_EXITED:
            // Drag exited view bounds
            // If called for trash can then reset it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            }
            view.invalidate();
            return true;
        case DragEvent.ACTION_DRAG_LOCATION:
            // Ignore this event
            return true;
        case DragEvent.ACTION_DROP:
            // Dropped inside view bounds
            // If called for trash can then delete the item and reload the grid
            // view
            if (view.getId() == R.id.trash_can) {
                drawables.remove(draggedIndex);
                draggedIndex = -1;
            }
            adapter.notifyDataSetChanged();
        case DragEvent.ACTION_DRAG_ENDED:
            // Hide the trash can
            new Handler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    findViewById(R.id.trash_can).setVisibility(View.GONE);
                }
            }, 1000l);
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            } else {
                view.setVisibility(View.VISIBLE);
            }
            // remove drag listeners
            view.setOnDragListener(null);
            return true;

        }
        return false;
    }

    @Override
    public boolean onItemLongClick(AdapterView gridView, View view,
            int position, long row) {
        ClipData.Item item = new ClipData.Item((String) view.getTag());
        ClipData clipData = new ClipData((CharSequence) view.getTag(),
                new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
        view.startDrag(clipData, new View.DragShadowBuilder(view), null, 0);
        View trashCan = findViewById(R.id.trash_can);
        trashCan.setVisibility(View.VISIBLE);
        trashCan.setOnDragListener(MainActivity.this);

        trashCan.setOnDragListener(MainActivity.this);
        draggedIndex = position;
        return true;
    }
}

 
And this is how it looks.
 

Advertisements

9 comments

  1. Pingback: Homepage
  2. thank you for this tutorial ! it help me a lot .
    but can i ask a question, how can i output what is being dropped? i mean when i drop the dog1 into trash can, string output will be displayed in other textfield saying “dog1 deleted”.

    thanks !!

    1. onItemLongClick you know the position of item being dragged. This value can be secured in member variables.
      In onDrag when you get DragEvent.ACTION_DROP you can use the value of position being dragged to identify the item and from your data set get the value and show it the way you like, you can use Toast maybe.

  3. Or can you provide the working source code for download? the view.setImageResources() keep giving me errors I can’t solve.

  4. I managed to make this work after endless trials. For anyone who face this problem; all you have to do is in you may have to suppress warnings and also in your getView(int, view) function, change your

    view.setImageResource(drawable.get(position)); to

    view.setImageResource((Integer) drawables.get(position));
    What you did here is you casted the drawable file to integer 1.

    Hope this helps.
    And thanks uploader!

  5. Im a newbie in android. Can u help me on how to modulate the program, when i use a separate image adapter class to create a grid view?? Thanks in advance 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s