(March 4, 2013 Updsate: This example has been revised for Android V4, See Drag-Drop for GridView V4.)
It’s time for another tutorial on drag-drop in Android. I have written several other articles on this topic (Part 1, Part 2, Part 3), but the last one was back in February. I have learned quite a bit since then.
This time I built a demo that allows you to add images dynamically to the screen and then drag them onto a GridView. You can then move the images around in the GridView or move them to the trash. The figures below show you what it looks like.
Figure 1 – Touching the “Add Image” button places an image below the 3 x 3 grid.
FIgure 2 – Touching the image for a second or two starts the drag-drop sequence. As you move over regions on the grid, the background color changes to green, indicating that the object can be dropped there.
Figure 3 – Image moves to cell when you remove your finger from the screen.
Figure 4 – A second image on its way to the middle cell in the grid.
Figure 5 – Three images on the grid.
Figure 6 – Images can be dragged to a trashcan to remove them from the screen.
Only one image can be in each cell, but the images can be dragged from one cell to another.
How It Works
I have already written extensively about the drag-drop framework in earlier blog articles. I refer you to them for full descriptions: (1) Moving Views in Android – Part 2, Drag and Drop; (2) Moving Views in Android – Part 3, Drop Zones. The first one tells the story of how I modified the classes and interfaces I found in the Android Launcher to get to a demo program. In the second one, I developed the notion of DropZone as a place on the screen where objects could be dropped. So, given those two notes, I’ll just cover the some of the major differences and improvements.
The first thing that’s different about my latest demo app is that it demonstrates dragging and dropping within one of the standard ViewGroup subclasses: GridView. The earlier work used the deprecated class AbsoluteLayout. At the time, I felt a little bad about the decision to use AbsoluteLayout, but I was eager to achieve my goal of moving views around on the screen and there was a lot to deal with to pare the Android Launcher classes down to a demo program so I used AbsoluteLayout.
Modifying my past demo programs to support dragging to a GridView was not much work at all. The drag-drop framework gave me the object vocabulary. I just had to find how to apply it to a commonly used layout. In this case, I chose to use a FrameLayout as the outermost layout with a GridView nested within it. So I started with DragLayer as a subclass of FrameLayout. The views in the cells of the GridView would have to implement the DropTarget interface if they were to allow objects to be dragged onto them. I also decided that they would implement the DragSource interface so objects could be dragged from one cell to another. Rather than start with views already on the screen, as I had done in the Part 2 demo, I thought I’d make the whole thing dynamic. Images would get added when the user clicks an Add Image button. So that meant there would be one more DragSource object added to the screen with each click.
I ended up with only a few new classes in this demo app. One of them is the ImageCell class, which as I said above, implements both the DragSource and DropTarget interfaces. The other new class is a DeleteZone class, which I used to implement the trashcan that you see on the screen.
With that as background, here’s what’s involved with a drag-drop operation.
- The user presses on an image on the screen.
- The view receives that as a “long click” event and passes that to an event handler via a call to onLongClick.
(Note: If you do not want long click to be the way to start a drag, see the newer version of this demo.) - The DragActivity object has already set itself up as a listener for long clicks for all the draggable views on the screen. So its onLongClick method is called.
- Code in onLongClick arranges to start handling of a drag-drop operation by calling a DragController.
A DragController is an object that handles all of the touch events on behalf of a DragLayer object. - The DragLayer, as a subclass of ViewGroup, has an implementation of onInterceptTouchEvent, thus ensuring that all touch events from any of its child views go through it. This is important because this is how the one object, the DragController, gets to work across the view boundary of any single child. (Be sure to read the description of onInterceptTouchEvent in class ViewGroup. As they say, it is a complicated interaction.)
- When a DragController starts the drag, it gets a bitmap it can display and uses it to create a DragView. The bitmap is built from the view that fielded the original long click event. So what you see moving on the screen is not the actual view, but something that looks like the view.
- As long as the user continues touching the screen, all the touch events go to the DragLayer, which passes them along to the DragController. The DragController tracks the movement and moves the DragView to keep up.
- During this time, any ImageCell (DropTarget) objects that are located at that spot on the screen are called with the onDragEnter, onDragExit methods defined by the DropTarget.
- When the user stops touching the screen, the DragController checks to see if there is a ImageCell (DropTarget) present and if that target is willing to accept a dropped view (via a call to acceptDrop).
- If the target accepts the drop, the DragController calls the ImageCell onDrop method and also calls the method onDropCompleted of ImageCell where the drag started, in order to to let it know that the drop completed. (I know that sounds a bit confusing but there are two methods, one for the DropTarget interface and the other for the DragSource interface. ImageCells have both.)
That’s the basic flow. In this demo, the ImageCell class is where most of the interesting code is. Let’s look more closely at that class and a few of its code segments. (Note: Link to full source code is at the end of this article.)
/**
* This subclass of ImageView is used to display an image on a GridView.
* An ImageCell knows which cell on the grid it is showing and which
* grid it is attached to. Cell numbers are from 0 to NumCells-1.
* It also knows if it is empty.
*
* Image cells are places where images can be dragged from and dropped onto.
* This class implements both the DragSource and DropTarget interfaces.
*/
public class ImageCell extends ImageView
implements DragSource, DropTarget
Two methods take care of changing the color of the cell as objects are dragged over it.
public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
int bg = mEmpty ? R.color.cell_empty_hover : R.color.cell_filled_hover;
setBackgroundResource (bg);
}
public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
int bg = mEmpty ? R.color.cell_empty : R.color.cell_filled;
setBackgroundResource (bg);
}
The onDrop method is responsible for filling the cell up with the dragged object. It grabs the drawable from the object being dragged and sets its own drawable to be the same thing.
public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
DragView dragView, Object dragInfo) {
// Mark the cell so it is no longer empty.
mEmpty = false;
int bg = mEmpty ? R.color.cell_empty : R.color.cell_filled;
setBackgroundResource (bg);
// The view being dragged does not actually change its parent
// and switch over to the ImageCell.
// What we do is copy the drawable from the source view.
ImageView sourceView = (ImageView) source;
Drawable d = sourceView.getDrawable ();
if (d != null) {
this.setImageDrawable (d);
}
}
In its role as a DragSource, an ImageCell, the one where the drag started, also gets its onDropCompleted called. In that method, the cell clears itself and sets it drawable to empty, thus erasing the previous contents.
public void onDropCompleted (View target, boolean success)
{
// If the drop succeeds, the image has moved elsewhere.
// So clear the image cell.
if (success) {
mEmpty = true;
if (mCellNumber >= 0) {
int bg = mEmpty ? R.color.cell_empty : R.color.cell_filled;
setBackgroundResource (bg);
setImageDrawable (null);
} else {
// For convenience, we use a free-standing ImageCell to
// take the image added when the Add Image button is clicked.
setImageResource (0);
}
}
}
For this to work, the DragLayer has to be set up correctly with the right DropTarget objects. The earlier demo programs defined all the DropTargets ahead of time in the layout xml file for the main activity. For this demo, the drop targets are not known ahead of time. They are ImageCell objects in a GridView. Those are allocated dynamically by an ImageCellAdapter in the onCreate method of DragActivity. It’s not particularly easy to get the elements on a GridView except by iterating through the child views of the GridView after they are all present on the screen. Since the DragLayer used by DragActivity is set up as a DragListener of the DragController, there is a perfect spot to set up all the drop targets that are found in the GridView.
public void onDragStart(DragSource source, Object info, int dragAction)
{
// We are starting a drag.
// Build up a list of DropTargets from the child views of the GridView.
// Tell the drag controller about them.
if (mGridView != null) {
int numVisibleChildren = mGridView.getChildCount();
for ( int i = 0; i < numVisibleChildren; i++ ) {
DropTarget view = (DropTarget) mGridView.getChildAt (i);
mDragController.addDropTarget (view);
}
}
// Always add the delete_zone so there is a place to get rid of views.
// Find the delete_zone and add it as a drop target.
// That gives the user a place to drag views to get them off the screen.
View v = findViewById (R.id.delete_zone_view);
if (v != null) {
DeleteZone dz = (DeleteZone) v;
mDragController.addDropTarget (dz);
}
}
All of the drop targets of the DragController are reset in the onDragEnd method.
Next Time
Overall, I think this demo worked out well. I wanted to address many of the comments that I had received from the earlier articles. People wanted to see an alternative for use of the deprecated AbsoluteLayout class, and they wanted to see an example that did not have as many limitations on where you could drag objects.
One thing that I still want to do is adapt this demo to the drag-drop framework described on the Android developers’ website: Dragging and Dropping. It looks like the DragLayer, DragController, DragSource, and DropTarget classes will work nicely with that. Look for more on that in a later article.
Source Code For This Demo
The source code for this demo application is available in two places: (1) download from Wglxy.com. (2) download from Google Docs. If you find that the app does not build in Eclipse, be sure to do a clean build by using the “Clean” item on the Project menu.
This demo app was built on a MacBook running Eclipse Galileo and Android 2.2_r3. The app has been tested in the Android emulator and on an LG Optimus phone.
There is also an improved version of this example application. It allows you to drag objects immediately. You do not have to wait for a long click. (In fact, those changes have been incorporated into this example too.) 







Recent Comments