While writing this post, a question was in my mind that how can I create a list where I can rearrange list items with dragging list rows to another row and so on?
I have a list of records in a listview that I want the user to be able to re-sort using a drag and drop method. I have seen this implemented in other apps, In iPhone apps too, It might be possible that others need this drag n drop feature as well.
From long time i have been trying to make drag and drop feature run on simple list view. For this, i searched over the internet and other resources and found that “TouchInterceptor.java” file from music app in android is a very useful to perform the dragging and dropping items within list view. i have used most of the implementation from here and developed my own DndListView feature on list view.
Here are the important files those are used in sample Drag n Drop feature developed for Android.
DnDListView.java
public class DndListView extends ListView { private Context mContext; private ImageView mDragView; private WindowManager mWindowManager; private WindowManager.LayoutParams mWindowParams; private int mDragPos; private int mFirstDragPos; private int mDragPoint; private int mCoordOffset; private DragListener mDragListener; private DropListener mDropListener; private int mUpperBound; private int mLowerBound; private int mHeight; private Rect mTempRect = new Rect(); private Bitmap mDragBitmap; private final int mTouchSlop; private int mItemHeightNormal; private int mItemHeightExpanded; private int dndViewId; private int dragImageX = 0; private int totalchilds = 0; public DndListView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();// etScaledTouchSlop(); totalchilds = getChildCount(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // Logger.debugE(“DndListView Intercept :” + ev.getAction()); if (getChildCount() > 0) totalchilds = getChildCount(); if (mDragListener != null || mDropListener != null) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: int x = (int) ev.getX(); int y = (int) ev.getY(); if (x < this.getWidth() – 50) { return false; } int itemnum = pointToPosition(x, y); System.out.println(“Item num :” + itemnum); if (itemnum == AdapterView.INVALID_POSITION) { break; } View item = (View) getChildAt(itemnum - getFirstVisiblePosition()); // item.setBackgroundColor(Color.RED); mItemHeightNormal = item.getHeight(); mItemHeightExpanded = mItemHeightNormal * 2; mDragPoint = y – item.getTop(); mCoordOffset = ((int) ev.getRawY()) – y; View dragger = item.findViewById(dndViewId); if (dragger == null) dragger = item; Rect r = mTempRect; dragger.getDrawingRect(r); if (x < r.right * 2) { item.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); startDragging(bitmap, y); mDragPos = itemnum; mFirstDragPos = mDragPos; mHeight = getHeight(); int touchSlop = mTouchSlop; mUpperBound = Math.min(y – touchSlop, mHeight / 3); mLowerBound = Math.max(y + touchSlop, mHeight * 2 / 3); return false; } mDragView = null; break; } } return super.onInterceptTouchEvent(ev); } public void redraw(int pos) { for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(i); if (i == pos) v.setBackgroundColor(Color.parseColor(“#E0E0E0″)); else v.setBackgroundColor(Color.WHITE); } } public void refresh() { long time = Long.parseLong((“” + System.nanoTime()).substring(0, 7)); MotionEvent me = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, getWidth() – 10, 300, 0); onInterceptTouchEvent(me); stopDragging(); } @Override public boolean onTouchEvent(MotionEvent ev) { if (getChildCount() > 0) totalchilds = getChildCount(); if ((mDragListener != null || mDropListener != null) && mDragView != null) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: Rect r = mTempRect; mDragView.getDrawingRect(r); stopDragging(); int y = (int) ev.getY(); int speed = 0; if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { mDropListener.drop(mFirstDragPos, mDragPos); if (mDragPos < (totalchilds-1) ) setSelectionFromTop(0, 0); } unExpandViews(false); break; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: int x = (int) ev.getX(); y = (int) ev.getY(); if (x < this.getWidth() – 50) { return false; } dragView(x, y); int itemnum = getItemForPosition(y); if (itemnum >= 0) { if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { if (mDragListener != null) { mDragListener.drag(mDragPos, itemnum); } mDragPos = itemnum; doExpansion(); } speed = 0; adjustScrollBounds(y); if (y > mLowerBound) { speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; } else if (y < mUpperBound) { speed = y < mUpperBound / 2 ? -16 : -4; } if (speed != 0) { int ref = pointToPosition(0, mHeight / 2); if (ref == AdapterView.INVALID_POSITION) { // we hit a divider or an invisible view, check // somewhere else ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); } View v = getChildAt(ref – getFirstVisiblePosition()); if (v != null) { int pos = v.getTop(); setSelectionFromTop(ref, pos – speed); } } } break; } return true; } return super.onTouchEvent(ev); } private int getItemForPosition(int y) { int adjustedy = y – mDragPoint – 32; int pos = myPointToPosition(0, adjustedy); if (pos >= 0) { if (pos <= mFirstDragPos) { pos += 1; } } else if (adjustedy < 0) { pos = 0; } return pos; } private int myPointToPosition(int x, int y) { Rect frame = mTempRect; final int count = getChildCount(); for (int i = count – 1; i >= 0; i–) { final View child = getChildAt(i); child.getHitRect(frame); if (frame.contains(x, y)) { return getFirstVisiblePosition() + i; } } return INVALID_POSITION; } private void adjustScrollBounds(int y) { if (y >= mHeight / 3) { mUpperBound = mHeight / 3; } if (y <= mHeight * 2 / 3) { mLowerBound = mHeight * 2 / 3; } } private void doExpansion() { int childnum = mDragPos – getFirstVisiblePosition(); if (mDragPos > mFirstDragPos) { childnum++; } View first = getChildAt(mFirstDragPos – getFirstVisiblePosition()); for (int i = 0;; i++) { View vv = getChildAt(i); if (vv == null) { break; } int height = mItemHeightNormal; int visibility = View.VISIBLE; if (vv.equals(first)) { if (mDragPos == mFirstDragPos) { visibility = View.INVISIBLE; } else { height = 1; } } else if (i == childnum) { System.out.print(“I :” + i); if (mDragPos < getCount() – 1) { height = mItemHeightExpanded; } } ViewGroup.LayoutParams params = vv.getLayoutParams(); params.height = height; vv.setLayoutParams(params); vv.setVisibility(visibility); } } private void unExpandViews(boolean deletion) { for (int i = 0;; i++) { View v = getChildAt(i); if (v == null) { if (deletion) { int position = getFirstVisiblePosition(); int y = getChildAt(0).getTop(); setAdapter(getAdapter()); setSelectionFromTop(position, y); } layoutChildren(); v = getChildAt(i); if (v == null) { break; } } ViewGroup.LayoutParams params = v.getLayoutParams(); params.height = mItemHeightNormal; v.setLayoutParams(params); v.setVisibility(View.VISIBLE); } } public void checkfordrop(int dragPos) { if (dragPos < totalchilds – 1) setSelectionFromTop(0, 0); } private void startDragging(Bitmap bm, int y) { stopDragging(); mWindowParams = new WindowManager.LayoutParams(); mWindowParams.gravity = Gravity.TOP; mWindowParams.x = dragImageX; mWindowParams.y = y – mDragPoint + mCoordOffset; mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; mWindowParams.format = PixelFormat.TRANSLUCENT; mWindowParams.windowAnimations = 0; ImageView v = new ImageView(mContext); int backGroundColor = Color.parseColor(“#e0103010″); v.setBackgroundColor(backGroundColor); v.setImageBitmap(bm); mDragBitmap = bm; mWindowManager = (WindowManager) mContext.getSystemService(“window”); mWindowManager.addView(v, mWindowParams); mDragView = v; } private void dragView(int x, int y) { mWindowParams.y = y – mDragPoint + mCoordOffset; mWindowManager.updateViewLayout(mDragView, mWindowParams); } private void stopDragging() { if (mDragView != null) { WindowManager wm = (WindowManager) mContext .getSystemService(“window”); wm.removeView(mDragView); mDragView.setImageDrawable(null); mDragView = null; } if (mDragBitmap != null) { mDragBitmap.recycle(); mDragBitmap = null; } } public void setDragListener(DragListener l) { mDragListener = l; } public void setDropListener(DropListener l) { mDropListener = l; } public void setDndView(int id) { dndViewId = id; } public void setDragImageX(int x) { dragImageX = x; } public interface DragListener { void drag(int from, int to); } public interface DropListener { void drop(int from, int to); } } |
ListDnD.java
public class ListDnD extends ListActivity implements DndListView.DragListener, DndListView.DropListener { /** Called when the activity is first created. */ private DndListView lvList; private EfficientAdapter mAdapter; private boolean isDnd = false; private ArrayList list = new ArrayList(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); lvList = (DndListView) this.getListView(); list.add(“http://www.wopll.com/“); list.add(“http://www.wopll.com/?p=368“); list.add(“http://www.wopll.com/?page_id=2“); list.add(“http://www.wopll.com/?feed=rss2“); list.add(“http://www.wopll.com/?cat=20“); mAdapter = new EfficientAdapter(this); setListAdapter(mAdapter); lvList.setDragListener(this); lvList.setDropListener(this); } private class EfficientAdapter extends BaseAdapter { private LayoutInflater mInflater; public EfficientAdapter(Context context) { mInflater = LayoutInflater.from(context); } public int getCount() { return list.size(); } public Object getItem(int pos) { return pos; } public long getItemId(int pos) { return pos; } public View getView(final int pos, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.simple_list_item_1, null); convertView.setBackgroundColor(Color.WHITE); holder = new ViewHolder(); holder.item = (TextView) convertView.findViewById(android.R.id.text1); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.item.setText(list.get(pos)); return convertView; } class ViewHolder { TextView item; } } @Override public void drag(int from, int to) { // TODO Auto-generated method stub if(!isDnd){ isDnd = true; Log.i(“Drag and Drop : drag”, “from : ” + from + “, to : ” + to); } } @Override public void drop(int from, int to) { // TODO Auto-generated method stub if(isDnd){ Log.i(“Drag and Drop : drop”, “from : ” + from + “, to : ” + to); if(from == to) return; String item = list.remove(from); list.add(to, item); isDnd = false; mAdapter.notifyDataSetChanged(); lvList.checkfordrop(to); } } } |
Source code for this demo applicaion is available here.
10 Responses to Android:Drag n Drop List View
Jitesh Rana
July 7th, 2011 at 10:32 am
nice article.
Filipe
July 7th, 2011 at 5:26 pm
Hi, the link to the source code is broken. Could you send it to my e-mail?
Also, does this code work on android 2.2.
Thanks very much!
wol
July 8th, 2011 at 10:30 am
Hi Filipe,
Link to the source code is corrected. you can download the source code from here
It should be working on android 2.2 also
Thanks for your interest!
keep visit.
Welcome
July 30th, 2011 at 7:38 am
That’s the best aswner of all time! JMHO
Ram
September 5th, 2011 at 10:09 am
Nice Article but i faced some issues, when i executed the app i noticed that Drag and Drop events are not getting fired. I haven’t modified any code.Can u explain what is the problem?? and i am executing the app in 2.2.
websizfz
September 5th, 2011 at 3:36 pm
Hi Balram,
Drag n Drop events fired from the right side, it works in 2.2 also, may be you are dragging it from the middle of the screen that is why you feed it does not works.
I have to do it like that, because if drag drop functionality in all listview area and i want to fire an event on single tap it does not works.
anyway keep visiting.
Yoav
September 15th, 2011 at 7:47 am
your code is the only one i found that the scrolling while dragging works well.. thank you for sharing!
gropapa
February 1st, 2012 at 2:50 pm
awesome source, i m a beginner in android and your code seems easy to understand. I’ll try to figure out why the drawing does’nt look exactly the same as the list item but still…. AWESOME
Girish
May 14th, 2012 at 6:04 am
Hi…
awesome source…
Thanks for sharing…
and one thing is we can drag it from right why not from the middle.. i’ll search for that any how its awesome..
ashwini
December 28th, 2012 at 10:23 am
Thanks for information.Nice article..