132

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, but I have not found a tutorial for it. It must be something that others need as well. Can anyone point me to some code for doing this?

miannelle
  • 2,181
  • 5
  • 19
  • 15
  • 1
    I've found this tutorial which might help [making a sortable list view](http://www.techrepublic.com/blog/australia/making-a-sortable-listview-in-android/708). I have not tested it yet but the video looks promising – Alex Sep 06 '12 at 18:11
  • @Arkde no kidding, they still haven't accepted an answer for this question, years later. – ArtOfWarfare Oct 10 '12 at 17:37
  • @ArtOfWarfare I guess one should consider that something unfortunate could have happened to the asker...disallowing further activity. – heycosmo Nov 11 '12 at 22:12
  • 1
    @heycosmo It is possible... according to their SO profile, miannelle last visited just a month after asking the question. Also, great work on the DSLV... I made a few modifications to it to allow things like double tap to duplicate items and changing the shadow of the item as it's dragged around (my items each have their row number on them, so I made it so the row number updates can update as it's dragged.) They're kind of just hacked in, far less elegant than everything else in the class, thus why I haven't submitted the changes to GitHub. – ArtOfWarfare Nov 12 '12 at 04:07
  • https://github.com/bauerca/drag-sort-listview i am using this and is it possible to drag a ROW onLongClick of Listview ? – Achin Feb 09 '15 at 12:33

6 Answers6

84

I have been working on this for some time now. Tough to get right, and I don't claim I do, but I'm happy with it so far. My code and several demos can be found at

Its use is very similar to the TouchInterceptor (on which the code is based), although significant implementation changes have been made.

DragSortListView has smooth and predictable scrolling while dragging and shuffling items. Item shuffles are much more consistent with the position of the dragging/floating item. Heterogeneous-height list items are supported. Drag-scrolling is customizable (I demonstrate rapid drag scrolling through a long list---not that an application comes to mind). Headers/Footers are respected. etc.?? Take a look.

heycosmo
  • 1,378
  • 10
  • 11
  • 3
    Your solution works like a charm. Much better then others. What is license of your source code? Apache 2.0? – Dariusz Bacinski Jul 03 '12 at 07:56
  • 1
    @heycosmo I have a few issues when creating a layout in my app using the views provided on the demo. Several namespaces error, could you please maybe do a small blogpost on how to use the code you provide? – daniel_c05 Feb 11 '13 at 18:24
  • Is there a possibility to modify code in a way that items are sorted not only on drop by while you are dragging your item above them? – Alex Semeniuk Mar 26 '13 at 13:43
  • @heycosmo Can you make your GitHub repository accessible? It seems to be offline... – sdasdadas May 12 '13 at 01:41
  • https://github.com/bauerca/drag-sort-listview i am using this and is it possible to drag a ROW onLongClick of Listview ? – Achin Feb 09 '15 at 12:33
  • 8
    The bauerca repo is no longer maintained. This fork is more active: https://github.com/JayH5/drag-sort-listview – ecdpalma Mar 30 '15 at 20:01
  • @ecdpalma I believe your answer is a bit misleading, or at least at the moment of writing this comment. The latest commit written in the repo you provided, is October 15, while the latest commit in heycosmos repo is March 16. I have just tried today, and it works like a charm for what it was intended. And while the DragLinearLayout he provided is not a ListView, it's highly usable, simple and it works. – xarlymg89 Aug 28 '18 at 09:42
24

Now it's pretty easy to implement for RecyclerView with ItemTouchHelper. Just override onMove method from ItemTouchHelper.Callback:

 @Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    mMovieAdapter.swap(viewHolder.getAdapterPosition(), target.getAdapterPosition());
    return true;
}

Pretty good tutorial on this can be found at medium.com : Drag and Swipe with RecyclerView

amukhachov
  • 5,566
  • 1
  • 37
  • 55
18

Am adding this answer for the purpose of those who google about this..

There was an episode of DevBytes (ListView Cell Dragging and Rearranging) recently which explains how to do this

You can find it here also the sample code is available here.

What this code basically does is that it creates a dynamic listview by the extension of listview that supports cell dragging and swapping. So that you can use the DynamicListView instead of your basic ListView and that's it you have implemented a ListView with Drag and Drop.

Arun C
  • 8,907
  • 1
  • 25
  • 42
  • 1
    When using DevBytes implementation keep in mind that your adapter and DynamicListView mush share the same instance if objects array. Otherwise implementation will not work. This is not a problem for static lists but may be a challenge with Loaders – AAverin Jan 12 '14 at 09:51
  • I tried the example on a current 5.0 Android version. It has some problems now... – Torsten B Jan 07 '15 at 09:16
  • @TCA Yeah, me also got issues on 5.0. Please help. – Manu Apr 16 '15 at 14:07
7

The DragListView lib does this really neat with very nice support for custom animations such as elevation animations. It is also still maintained and updated on a regular basis.

Here is how you use it:

1: Add the lib to gradle first

dependencies {
    compile 'com.github.woxthebox:draglistview:1.2.1'
}

2: Add list from xml

<com.woxthebox.draglistview.DragListView
    android:id="@+id/draglistview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3: Set the drag listener

mDragListView.setDragListListener(new DragListView.DragListListener() {
    @Override
    public void onItemDragStarted(int position) {
    }

    @Override
    public void onItemDragEnded(int fromPosition, int toPosition) {
    }
});

4: Create an adapter overridden from DragItemAdapter

public class ItemAdapter extends DragItemAdapter<Pair<Long, String>, ItemAdapter.ViewHolder>
    public ItemAdapter(ArrayList<Pair<Long, String>> list, int layoutId, int grabHandleId, boolean dragOnLongPress) {
        super(dragOnLongPress);
        mLayoutId = layoutId;
        mGrabHandleId = grabHandleId;
        setHasStableIds(true);
        setItemList(list);
}

5: Implement a viewholder that extends from DragItemAdapter.ViewHolder

public class ViewHolder extends DragItemAdapter.ViewHolder {
    public TextView mText;

    public ViewHolder(final View itemView) {
        super(itemView, mGrabHandleId);
        mText = (TextView) itemView.findViewById(R.id.text);
    }

    @Override
    public void onItemClicked(View view) {
    }

    @Override
    public boolean onItemLongClicked(View view) {
        return true;
    }
}

For more detailed info go to https://github.com/woxblom/DragListView

Wox
  • 133
  • 1
  • 7
5

I found DragSortListView worked well, although getting started on it could have been easier. Here's a brief tutorial on using it in Android Studio with an in-memory list:

  1. Add this to the build.gradle dependencies for your app:

    compile 'asia.ivity.android:drag-sort-listview:1.0' // Corresponds to release 0.6.1
    
  2. Create a resource for the drag handle ID by creating or adding to values/ids.xml:

    <resources>
        ... possibly other resources ...
        <item type="id" name="drag_handle" />
    </resources>
    
  3. Create a layout for a list item that includes your favorite drag handle image, and assign its ID to the ID you created in step 2 (e.g. drag_handle).

  4. Create a DragSortListView layout, something like this:

    <com.mobeta.android.dslv.DragSortListView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:dslv="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        dslv:drag_handle_id="@id/drag_handle"
        dslv:float_background_color="@android:color/background_light"/>
    
  5. Set an ArrayAdapter derivative with a getView override that renders your list item view.

        final ArrayAdapter<MyItem> itemAdapter = new ArrayAdapter<MyItem>(this, R.layout.my_item, R.id.my_item_name, items) { // The third parameter works around ugly Android legacy. http://stackoverflow.com/a/18529511/145173
            @Override public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);
                MyItem item = getItem(position);
                ((TextView) view.findViewById(R.id.my_item_name)).setText(item.getName());
                // ... Fill in other views ...
                return view;
            }
        };
    
        dragSortListView.setAdapter(itemAdapter);
    
  6. Set a drop listener that rearranges the items as they are dropped.

        dragSortListView.setDropListener(new DragSortListView.DropListener() {
            @Override public void drop(int from, int to) {
                MyItem movedItem = items.get(from);
                items.remove(from);
                if (from > to) --from;
                items.add(to, movedItem);
                itemAdapter.notifyDataSetChanged();
            }
        });
    
Edward Brey
  • 35,877
  • 14
  • 173
  • 224
4

I recently stumbled upon this great Gist that gives a working implementation of a drag sort ListView, with no external dependencies needed.


Basically it consists on creating your custom Adapter extending ArrayAdapter as an inner class to the activity containing your ListView. On this adapter one then sets an onTouchListener to your List Items that will signal the start of the drag.

In that Gist they set the listener to a specific part of the layout of the List Item (the "handle" of the item), so one does not accidentally move it by pressing any part of it. Personally, I preferred to go with an onLongClickListener instead, but that is up to you to decide. Here an excerpt of that part:

public class MyArrayAdapter extends ArrayAdapter<String> {

    private ArrayList<String> mStrings = new ArrayList<String>();
    private LayoutInflater mInflater;
    private int mLayout;

    //constructor, clear, remove, add, insert...

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        View view = convertView;
        //inflate, etc...

        final String string = mStrings.get(position);
        holder.title.setText(string);

        // Here the listener is set specifically to the handle of the layout
        holder.handle.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                    startDrag(string);
                    return true;
                }
                return false;
            }
        });

        // change color on dragging item and other things...         

        return view;
    }
}

This also involves adding an onTouchListener to the ListView, which checks if an item is being dragged, handles the swapping and invalidation, and stops the drag state. An excerpt of that part:

mListView.setOnTouchListener(new View.OnTouchListener() {
     @Override
     public boolean onTouch(View view, MotionEvent event) {
        if (!mSortable) { return false; }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                // get positions
                int position = mListView.pointToPosition((int) event.getX(), 
                    (int) event.getY());
                if (position < 0) {
                    break;
                }
                // check if it's time to swap
                if (position != mPosition) {
                    mPosition = position;
                    mAdapter.remove(mDragString);
                    mAdapter.insert(mDragString, mPosition);
                }
                return true;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE: {
                //stop drag state
                stopDrag();
                return true;
            }
        }
        return false;
    }
});

Finally, here is how the stopDrag and startDrag methods look like, which handle the enabling and disabling of the drag process:

public void startDrag(String string) {
    mPosition = -1;
    mSortable = true;
    mDragString = string;
    mAdapter.notifyDataSetChanged();
}

public void stopDrag() {
    mPosition = -1;
    mSortable = false;
    mDragString = null;
    mAdapter.notifyDataSetChanged();
}
DarkCygnus
  • 5,985
  • 3
  • 33
  • 52
  • While this seems to be one of the easiest implementations, it looks bad(rows just switch, there's actually zero looks), it feels bad and makes scrolling through a list that doesn't fit the screen nigh impossible (I constantly switch rows instead of scrolling). – Heinzlmaen Dec 05 '19 at 14:09