Try PandaPow for Android
By date:

RSS Feed

Skip this one

Android Drag and Drop

2010-06-25 19:54:17

A fundamental UI feature such as Drag and Drop should be trivial to implement, right? In fact, on Android it isn't that difficult (depending on how flexible you wanna be of course). That said, it does require that you implement your own view class(es) so if that turns you off, then you'd better stop reading now :)

Most drag and drop references I've found online refer to the source code of the Android Media Player. It's TouchInterceptor class implements a drag and drop of elements in a list. Another resource, which provides a more generic, but also more complicated, drag and drop is the Android Launcher app.

Since I wanted more than just a list rearrangement, I decided to follow the example of the Launcher, albeit much much simpler. The basic idea is to introduce a class, called DragLayer, that extends FrameLayout and which takes care of most of the actual dragging. Unlike in the Launcher, however, I would restrict the DragLayer to only moving the object around, and let whatever user of the layer take care of things like scrolling, dropping it in place, et c. Also, the drag layer of the Launcher creates a copy of the view that is to be moved, rather than moving the actual view. This is obviously useful in many cases, but not always necessary, so I decided to make that optional.

A key in implementing the DragLayer class is understanding the interaction between the touch detection callbacks that exists in a ViewGroup. They are:

  • boolean onTouchEvent(MotionEvent ev) - called whenever a touch event with this View as target is detected
  • boolean onInterceptTouchEvent(MotionEvent ev) - called whenever a touch event is detected with this ViewGroup or a child of it as target. If this function returns true, the MotionEvent will be intercepted, meaning it will be not be passed on to the child, but rather to the onTouchEvent of this View.

So in order to for our DragLayer to take charge of the drag movements, it needs to implement these two functions. In addition I let it implement two functions startDragging and stopDragging as given in the below outline. This example only handles movement on the x-axis, but that's should be very straight forward to extend:

class DragLayer extends FrameLayout {
    ...

   // Called by user with a child of this DragLayer to be 
   // dragged
   public void startDragging(View child) {
      // Do initial setup to enable moving the child
      mStartX=mLastMotionX;
      mStartRightMargin = getWidth()-child.getRight();
      mDragView=child;
      doDrag(mStartX);

      // Indicate that we are now in dragging mode.
      mIsDragging = true;
   }

   // Moves the object by changing the right margin
   void doDrag(int x) {
      View v=mDragView;
      LayoutParams params = new LayoutParams(v.getLayoutParams());
      params.gravity = Gravity.RIGHT;
      params.rightMargin = (int) (mStartX-x) + mStartRightMargin;
      v.setLayoutParams(params);
   }

   // Called internally when dragging is stopped
   protected void stopDragging(int x) {
      if (mIsDragging) {
          mIsDragging = false;
          // Call back to user
          callStopCallback(x);
      }
   }

   @Override
   public boolean onInterceptTouchEvent(MotionEvent ev) {
      final int action = ev.getAction();

      final float x = ev.getX();

      switch (action) {

         case MotionEvent.ACTION_DOWN:
            // Remember each down event, 
            // as it is needed in startDragging above 
            mLastMotionX = x;
            break;

         case MotionEvent.ACTION_CANCEL:
         case MotionEvent.ACTION_UP:
            // User stopped dragging, or some other view
            //  has taken over the motion event 
            // Normally this is handled by onTouchEvent below, 
            // but it seems safest to also handle it here
            stopDragging(x);
            break;
      }
      // Whilst dragging, we return true in order to dispatch
      // the MotionEvent to our onTouchEvent method below.
      return mIsDragging;
   }

   @Override
   public boolean onTouchEvent(MotionEvent ev) {
      if (!mIsDragging) {
         // Not in dragging mode, so no action taken
         return super.onTouchEvent(ev);
      }    

      final int action = ev.getAction();
      float x = ev.getX();

      switch (action) {

         case MotionEvent.ACTION_MOVE:
            // Move the object.
            doDrag(x);
            // Call back to user
            callMoveCallback(x);
            break;

         case MotionEvent.ACTION_CANCEL:
         case MotionEvent.ACTION_UP:
            stopDragging(x);
            break;
       }
       return true;
   }

    ...
}

A basic user of this class could be an activity with the following layout:

<com.example.dragndrop.DragLayer ...>
  <TextView android:text="A" ...>
  <TextView android:text="B" ...>
  <TextView android:text="C" ...>
</com.example.dragndrop.DragLayer>

When the user clicks on any of the TextView children, the activity would call startDragging which would let the user move the view around.

As mentioned, the above is rough outline, just to illustrate the ideas of the basic machinery. That said, a more complete solution won't be much more complex.

If you want to see an example of a more complete DragLayer in action you can check out our app Amusing Snippets. Its layout includes a tool bar, which is a DragLayer containing a list of icons. An icon in the tool bar can be moved around inside the tool bar after long pressing it.

Page 1