This is an extension of the Android ListView that enables drag-and-drop re-sorting of list items. The code is based on the TouchInterceptor (TI) from the Google Music app (silly if it weren't!); therefore, the essential behavior is the same---list item heights and visibilities are adjusted to create an empty slot that tracks the item being dragged. User-provided Listener objects are informed of drag and drop events; these Listeners perform the actual reorderings within the user's ListAdapter.
While using the TI in an app of mine, I noticed the following behaviors that I thought needed polishing (in order of importance):
- Scrolling while dragging is erratic. A scroll is initiated in the TI only when a MOVE event is detected. I don't think this is a user-expected behavior.
- List item View heights must be homogeneous and pre-specified.
- Shuffling of list items is buggy for some drag movements (e.g. shuffle occurs only after large overlap of floating View with visible list items, last/first item in list does not shuffle, etc.).
- The list view sometimes jumps unexpectedly when dropping the floating View.
- Region for item drag initiation is hard-coded in the TI.
- Floating View is not bounded to ListView (maybe not such an issue, mostly aesthetic).
The above shortcomings caused a major reworking of the TI implementation details, resulting in DragSortListView (DSLV). I see a lot of potential in a clean drag-sort list; any app with a user-created list (e.g. "favorites") should benefit.
- Scrolling while dragging is now intuitive and easily customizable.
- Arbitrary item heights are supported.
- Dragging/Dropping/Drag-scrolling are mostly clean.
- (see 3)
- Drag initiation is customizable at the per-item level.
- Bounds on floating View (big whoop?)
I hope you find it useful! And please, help me improve the thing!
The best place to learn DSLV semantics is in the
So, as a first step, I recommend building the examples, playing with
them, and then exploring
demo/res/ for the
details. That said, the following is a brief overview of DSLV usage.
The DSLV can be declared in an XML layout file just like the ListView. Here is the example from the demo that shows all available attributes.
<?xml version="1.0" encoding="utf-8"?> <com.mobeta.android.dslv.DragSortListView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dslv="http://schemas.android.com/apk/res/com.mobeta.android.demodslv" android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingTop="20dp" android:paddingBottom="20dp" dslv:collapsed_height="1dp" dslv:drag_scroll_start="0.33" dslv:max_drag_scroll_speed="0.3" dslv:float_background_color="#000000" dslv:remove_mode="none" dslv:track_drag_scroll="false" />
The attributes are
collapsed_height: (dimension) Height of placeholder at original drag position.
drag_scroll_start: (float) Start of drag-scroll regions (defined by a fraction of the total DSLV height).
max_drag_scroll_speed: (float) Maximum drag-scroll speed for default linear drag-scroll profile. Units of pixels/millisecond.
float_background_color: (color) Background color of floating View.
remove_mode: (enum) One of "none" "fling", "slide", "slideRight", "slideLeft". This is inherited from the TI and may change.
track_drag_scroll: (bool) Debugging option; explained below.
Drag-sorting in the DSLV is enabled when:
DragSortListView.DropListeneris registered with the DSLV instance and
- A child View of the list item to be dragged has
If you have ever used the TI, the Drag and Drop Listeners should be
familiar. Otherwise, there is limited documentation in the DSLV.
You can check it
out with Javadoc by navigating to
javadoc com.mobeta.android.dslv *
I imagine most use cases will require only a DropListener to perform the
ListAdapter reordering. To register,
simply pass it to
To illustrate the second requirement, the following is an example XML layout file for a ListView item and can be found in the demo project:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:paddingRight="5dp"> <TextView android:id="@+id/artist_name_textview" android:layout_width="wrap_content" android:layout_height="40dp" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/artist_albums_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> <ImageView android:id="@id/drag" android:background="@drawable/drag" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_weight="0" /> </LinearLayout>
Okay, so all of the above is just fluff except for the line
which tells DSLV which child View is responsible for initiating the
item drag. You will notice that
@id is used rather than
This is because the demo project references the DSLV as an external
Android library, in which the id named
drag is already defined.
In fact, when used as a standalone library, the DSLV cannot access
ids defined by the dependent app.
Okay, maybe not all fluff; the above layout file is an example of how
DSLV can handle arbitrary list item heights! Notice that the enclosing
"wrap_content" as its layout_height. This means
the height of the list item is determined by how much text is in
there. Check out the demo!
Another way to use the DSLV is by copying the DragSortListView.java file directly into your project. In this case, you must also:
android:id="@+id/drag"(notice the +) in your list item layout file OR copy
res/values/ids.xmlto your project and use
- Change the package name declaration line at the top of DragSortListView.java to your package name.
res/values/dslv_attrs.xmlto your project
- In the XML layout file that declares the DSLV, make sure to use
your package name (as opposed to
com.mobeta.android.(demo)dslvin the example above)
Download and install the Android sdk. Clone/Download/Fork the repo through GitHub or via (read-only)
git clone https://github.com/bauerca/drag-sort-listview.git
Navigate to drag-sort-listview/ and type (assuming /path/to/android_sdk/tools is in your PATH)
android update project --path ./ --subprojects
Then, navigate to drag-sort-listview/demo/, build, and try out the examples.
If you have python and matplotlib installed, you can use the script dslv.py to debug drag scroll behavior (a drag scroll occurs when a list item is dragged to the edge of the ListView, causing a scroll). This script is found in the project tools/ directory.
To enable, just set the
dslv:track_drag_scroll attribute to
"true" in XML. While drag scrolling on your emulator or device,
this tracking causes the DSLV to periodically dump its state to
a file called dslv_state.txt in the device/emulator /sdcard/ directory.
Navigate to the location of dslv.py, and do
adb [-e|-d|-s device] pull /sdcard/dslv_state.txt
then simply run
An image should appear that represents the DSLV in the final recorded state. Right and left keys allow stepping through the recorded drag scroll frame-by-frame. This tool has been very useful for debugging jumpy drag scroll behavior.