Wednesday, March 02, 2011

Honeycomb Drag and Drop Basics

If you are thinking about developing an Android tablet app, one of the most exciting things has got to be the drag and drop capabilities that were added to Android as part of Honeycomb. No wait a minute, aren't there a lot more interesting features than drag and drop in Honeycomb? There are definitely a lot of great features, but drag and drop is one of the most interesting in my opinion. It's a perfect fit for tablets, where you can have this nice big graphical objects to drag and drop. I think it really challenges mobile designers and developers to rethink our interaction models. If you read through that tutorial I just linked to, you will learn about many of the intricacies of drag and drop Android style. I thought I'd provide an even simpler example for those of you who prefer the Cliff Notes versions of things.

First we will start with a simple UI, a 2x2 grid with a cute little image that we will drag and drop around this grid. Here is what it looks like:

DnD UI

I used a simple TableLayout for this, along with some basic border background Drawables. Here's the XML for it:

<TableLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TableRow>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/topLeft"
        android:background="@drawable/border">
        <ImageView android:id="@+id/droid"
            android:src="@drawable/bugdroid"
            android:layout_width="150px"
            android:layout_height="150px"
         />
    </LinearLayout>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/topRight"
        android:background="@drawable/border2"
    />    
  </TableRow>
  <TableRow>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/bottomLeft"
        android:background="@drawable/border2"
    />
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/bottomRight"
        android:background="@drawable/border"
    />    
  </TableRow>  
</TableLayout>

Nothing special so far. All of the magic happens in the code. Here's the basics of the Activity that uses this XML.

public class DndActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.grid);
  View droid = findViewById(R.id.droid);
  droid.setOnLongClickListener(new OnLongClickListener(){
   @Override
   public boolean onLongClick(View v) {
    ClipData data = ClipData.newPlainText("foo","bar");
    DragShadowBuilder shadowBuilder = new DragShadowBuilder();
    v.startDrag(data, shadowBuilder, v, 0);
    return true;
   }
  });
  findViewById(R.id.topLeft).setOnDragListener(new BoxDragListener());
  findViewById(R.id.bottomLeft).setOnDragListener(new BoxDragListener());
  findViewById(R.id.topRight).setOnDragListener(new BoxDragListener());
  findViewById(R.id.bottomRight).setOnDragListener(new BoxDragListener());
 }
}

Here we just set the contentView, then we get a handle on the droid image. We set its onLongClickListener. I think the long click will become the de facto way to initiate drag and drop in Android apps. You could use some other event of course, but long click feels right. The key thing we need to do to initiate drag and drop is invoke the startDrag method. To do that, we have to create a ClipData object and DragShadowBuilder. You can use the ClipData to put all kinds of interesting data/metadata that will be passed around to the drop zones (Views) in your app. In this case I didn't need anything fancy, so I put something minimal and arbitrary. For the DragShadowBuilder, you might want to do something fancy to make the dragging look cool. I used a default one and it doesn't do much at all. You also need to set a "localData" object. This can be anything. Since my local data is the image itself, I chose to pass the ImageView that will be passed in to the onLongClick method. Now you can call startDrag. Finally, make sure you return true to let the OS know that drag and drop is a go.

Next in the code you will see that for each of the cells in my table, I have set an OnDragListener object. I've set this to a new instance of BoxDragListener for each of the cells. This is an inner class of DndActivity:

class BoxDragListener implements OnDragListener{
 boolean insideOfMe = false;
 Drawable border = null;
 Drawable redBorder = getResources().getDrawable(R.drawable.border3);
 @Override
 public boolean onDrag(View self, DragEvent event) {
  if (event.getAction() == DragEvent.ACTION_DRAG_STARTED){
   border = self.getBackground();
   self.setBackgroundDrawable(redBorder);
  } else if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED){ 
   insideOfMe = true;
  } else if (event.getAction() == DragEvent.ACTION_DRAG_EXITED){
   insideOfMe = false;
  } else if (event.getAction() == DragEvent.ACTION_DROP){
   if (insideOfMe){
    View view = (View) event.getLocalState();
    ViewGroup owner = (ViewGroup) view.getParent();
    owner.removeView(view);
    LinearLayout container = (LinearLayout) self;
    container.addView(view);
   }
  } else if (event.getAction() == DragEvent.ACTION_DRAG_ENDED){
   self.setBackgroundDrawable(border);     
  }
  return true;
 }
}

This guy will get a stream of DragEvents fired at its onDrag method. You can determine what kind of DragEvent it is by calling getAction on the event. The first one you will get is an ACTION_DRAG_STARTED event. For this we want to change the border of our cell to red to let the user know that they can drop the image in this cell. Here's what that looks like:

Enter the red zone

Next we want to keep track if the user's finger has entered into a drop zone. So we look for the ACTION_DRAG_ENTERED and toggle a local boolean. Similarly, we look for ACTION_DRAG_EXITED in case the user changed their mind and picked a different zone to drop the image into. Next we look for the ACTION_DROP. If the object is inside our drop zone, then we update the UI by removing it from its previous parent and adding it to the drop zone. Finally, we listen for ACTION_DRAG_ENDED to know when the drag and dropping is finished. We then restore the borders to their original color. That's it!

Update: The code above will not draw a shadow when you drag the image around the screen. There is a very easy way to get a shadow and that is to pass the View to it the DragShadowBuilder constructor, i.e. new DragShadowBuilder(v); in the above code. That will cause the image in the sample app to be used as the shadow. Be careful using this technique though. If the View that you are dragging is big/complex, then you might take a performance hit (UI becomes sluggish.) In that case you might want to subclass DragShadowBuilder and override its onDragShadow method to draw something simpler for the shadow. I've been playing around with this and StackViews however, and it has worked great with no need to subclass DragShadowBuilder. Be sure to set the android:hardwareAccelerated="true" attribute in your AndroidManifest.xml either on the Activity or the whole Application.

19 comments:

Lars Vogel said...

Thanks a lot Michael, very nice introduction into D&D.

Mitoman said...

Hey, I am just wondering if you posted this project on github or anything. It's really annoying trying to figure out what I am doing wrong without the actual source code...

sravan said...

Hi Michael u r blog is very interesting ,
pleases provide code for borders how to draw for liner layout which r used in drag and drop

sarfaraz said...

Even i want to have that code........I need it!!!!!!

bfletch said...

Hey Sarfaraz,

The source code for this app is available on the googlecode page for the "Android in Practice" book, chapter 15 DragAndDrop...but beware that the author's code will crash after around 30 drag and drop operations (when you drag the image between the layout containers). Ive asked about this to the authors both here (comment deleted last week) and on the book's author forum (so far ignored) but Im not sure if they want to fix it.

cheers
Bruno

Unknown said...

@sarfaraz This code is a simplified version of code from Android in Practice. You can find a more complete version here: http://code.google.com/p/android-in-practice/source/browse/#svn%2Ftrunk%2Fch15%2FDragAndDrop

@bfletch Thanks for finding the crash that happens after repeated drag-and-drops. Unfortunately I have not had a chance to diagnose the problem yet. Have you tried to debug it or have any ideas that you'd like to share?

bfletch said...

Hi Mike,

I've spent many hours trying to find the solution to this problem. Up to now I've been unsuccessful. It is a pity since it is such a nice and simple example. I've been looking at HoneycombGallery example (http://developer.android.com/resources/samples/HoneycombGallery/index.html) and comparing it to your code. I'm trying to figure out why your DragAndDrop events unresponsive after a while, and what makes the HoneycombGallery example not crash (i.e., what are both codes doing differently). It appears that the HoneycombGallery app is using drag drop to exchange data, while your drag and drop code is used exchanges views. I'm wondering if the API for dragging views is not well supported yet. I will definitely let you know if I find a solution.

Cheers,
Bruno

sarfaraz said...
This comment has been removed by the author.
sarfaraz said...
This comment has been removed by the author.
sarfaraz said...

Hi bfletch & Michael Galpin
The Drag and Drop tutorial which you guys have linked is working properly but I want to make it workable for 8x8 ...can you guys help me with that....

sarfaraz said...

And one more thing I want to ask you is that..this application doesn't run for android2.2 can you tell me how to make workable for V2.2...Waiting for your replies....
Thanks and Regard
Sarfaraz

bfletch said...

Hi Sarfaraz,

Sorry for the delay. You can try using this guys library:

http://doffm.posterous.com/drag-and-drop-in-android-20

I haven't used it, so if you have any questions you should ask the author for the code.


Mike,

I was unable to find out what makes your code crash. I was able though to create a drag and drop app for Honeycomb based on the Pro Android 3 book that did not crash. I would recommend for you to look at the source code for their example on chapter 31 and possibly fix your code before your books is released.

Regards,
Bruno

Bruno

sarfaraz said...

Hi mike I am waiting for your reply!!!!!!!!!!!!!!!

Abdul Maazeed said...
This comment has been removed by the author.
Abdul Maazeed said...

Hi Mike,

This post was very helpful for drag and drop .On top of this, i have to develop in such a way that "i have to drop a view only in my desired layout than only the view from parent layout must remove otherwise remains same ".Can u plz give some idea on this .

creed said...

i am new to android development some please help me with very basic examples of drag and drop

Unknown said...

Small Business Management Software

Unknown said...
This comment has been removed by the author.
Android application developers said...

Thanks 4 sharing this..