An Android library to enable swipe to dismiss and undo functionality on ListViews.
Java
Latest commit f71bdaa Sep 24, 2013 @timroes Added deprecation notice

README.md

SwipeToDismissUndoList

This library is deprecated in favor of EnhancedListView. I won't fix any bugs on this library anymore.

The SwipeToDismissUndoList is a library to add swipe to dismiss functionality to a ListView and undo deletions again. The lib is based on Jake Wharton's SwipeToDismissNOA that is based on Roman Nurik's SwipeToDismiss sample.

The code is licensed under the Apache License, Version 2.0.

You can view a demonstration of this lib in the Demonstration App. The source code of this demonstration app can be found in the SwipteToDismissUndoDemo project.

Changes

2013-05-28 Introduced setSwipeDirection and improved detection of swipes.

2013-05-13 Use AbsListView instead of ListView to make this library also available for GridViews

Changes required: You need to change the parameter in the onDismiss method to AbsListView (see below).

2013-03-22 Added minimum SDK version to manifest

2013-02-24 Properly discard undo items (BUG FIX)

Usage

Add this project as an Android library to your project. The project should work from API level 3 upwards. If you find that this isn't true (might be, that I missed some newer methods), please inform me about that :)

To use the list create a regular ListView (e.g. via a ListActivity) and wrap it up in the SwipeDismissList of this lib:

ListView listView = // ... (findById or getListView)
SwipeDismissList.OnDismissCallback callback = // .. see below
UndoMode mode = // .. see below
SwipeDismissList swipeList = new SwipeDismissList(listView, callback, mode);

You would normally want to do that in your onCreate method.

OnDismissCallback

The second parameter to the constructor of SwipeDismissList is an OnDismissCallback. You must implement that, to handle the deletion of elements:

SwipeDismissList.OnDismissCallback callback = new SwipeDismissList.OnDismissCallback() {
    public SwipeDismissList.Undoable onDismiss(ListView listView, int position) {
        // Delete the item from your adapter (sample code):
        final String itemToDelete = mAdapter.get(position);
        mAdapter.remove(itemToDelete);
        return null;
    }
}

If you return null from the onDismiss method, your deletion won't be undoable. To make your deletion undoable, you must return a valid Undoable (implementing at least its undo method), that restores the element again:

SwipeDismissList.OnDismissCallback callback = new SwipeDismissList.OnDismissCallback() {
    public SwipeDismissList.Undoable onDismiss(AbsListView listView, final int position) {
        // Delete the item from your adapter (sample code):
        final String itemToDelete = mAdapter.get(position);
        mAdapter.remove(itemToDelete);
        return new SwipeDismissList.Undoable() {
            public void undo() {
                // Return the item at its previous position again
                mAdapter.insert(itemToDelete, position);
            }
        };
    }
}

You can override getTitle in the Undoable to provide an individual title for the item, that has been deleted. That title will be shown beside the undo button in the popup. If you don't override that method (or it returns null) the default deletion message will be shown in the popup. You can change this message with SwipeDismissList.setUndoString(String).

You can return null from the onDismiss method in general to disable undo on the list or just on special items, you don't want (or cannot) undo.

Notification about final delete

If you want to get notified when the user doesn't have a chance to make the undo anymore, meaning the popup dialog vanished (see below), just override the Undoable.discard() method in your Undoable. This will be called as soon as the popup dialog vanishes. You can e.g. really delete items from the database in that callback, that was just hidden beforehand.

If you implement the discard method you need to call SwipeDismissList.discardUndo() in the onStop method of your Activity. (See also Leaky Popup below). Otherwise some items might not receive the discard call, when e.g. the device is rotated.

Complete onDismissCallback example

An (pseudo) example of a complete OnDismissCallback:

SwipeDismissList.OnDismissCallback callback = new SwipeDismissList.OnDismissCallback() {
    // Gets called whenever the user deletes an item.
    public SwipeDismissList.Undoable onDismiss(AbsListView listView, final int position) {
        // Get your item from the adapter (mAdapter being an adapter for MyItem objects)
        final MyItem deletedItem = mAdapter.getItem(position);
        // Delete item from adapter
        mAdapter.remove(deletedItem);
        // Return an Undoable implementing every method
        return new SwipeDismissList.Undoable() {

            // Method is called when user undoes this deletion
            public void undo() {
                // Reinsert item to list
                mAdapter.insert(deletedItem, position)
            }

            // Return an undo message for that item
            public String getTitle() {
                return deletedItem.toString() + " deleted";
            }

            // Called when user cannot undo the action anymore
            public void discard() {
                // Use this place to e.g. delete the item from database
                finallyDeleteFromSomeStorage(deletedItem);
            }
        };
    }
};

setAutoHideDelay

The undo popup will be hidden automatically after some time, after the user has touched the screen after the deletion. The delay until it will hide is by default 5 seconds. You can change that value with the setAutoHideDelay(int) method, that takes a new delay in milliseconds.

setSwipeDirection

By default the user can swipe an item left or right to delete it. With this method you can limit it to the left or right side. See the Javadoc of setSwipeDirection and SwipeDismissList.SwipeDirection for further information.

UndoMode

The undo list can handle multiple undos in three different ways. You define the way with the third constructor parameter. If you don't pass in an argument, the default mode will be SwipeDismissList.UndoMode.SINGLE_UNDO.

SwipeDismissList.UndoMode.SINGLE_UNDO

Only the last deletion can be undone. As soon as the user deletes another item from the list, this will be undoable, but the previous won't be anymore.

SwipeDismissList.UndoMode.MULTI_UNDO

This mode is a multilevel undo. When the user deletes an item, while there is still the undo popup shown for a previous deleted one, both items will be saved for undo. Pressing now undo will undo the last deletion. Pressing undo after that the deletion that has done before, and so on ...

As soon as the undo popup vanishes all stored undos will be discarded.

By default the undo popup shows a message with the amount of deleted items in this mode. You can change this message with the setUndoMultipleString(String). This message can contain one placeholder (%d) that will be filled with the amount of stored undos.

If you pass null to that method, the undo popup will always show the individual message (returned by Undoable.getTitle()) for the last deletion (the one that will be undone, by a click on undo). If there is no individual message for an Undoable the default message (setUndoString(String)) will be shown instead.

SwipeDismissList.UndoMode.COLLAPSE_UNDO

This mode collapsed multiple undos into one. When the user deletes an item, while an undo popup is already shown, the new undo is stored. From now on the user sees an "Undo all" instead of "Undo" button. A click on that will undo all stored deletions at once.

If the popup vanished (due to the auto hide delay passed) all stored undos will be discarded.

You can again use setUndoMultipleString(String) to set an individual message. Also passig null is possible, but doesn't make too much sense in that case, since the user will only see the last undo messgae, but all deletions will be undone.

Customizing and Internationalization

If you want to customize the look and feel, just modify the resources as you like.

You can internationalize the "Undo" and "Undo all" string, in your own resources by adding a string for the name "undo" and one for "undoall". This library only provides the english translation.

You can pause the dismiss behavior of the list for some time by using the setEnabled(boolean) method on the SwipeDismissList.

Bugs

For bugs and feature requests please use the GitHub issue tracker. I haven't limited the library to any android api versions, but it might be, that it doesn't work on very old versions, so please feel free to tell me via an issue, I will then try to fix it.

Leaky Popup

When the popup is shown and the device is rotate, it causes an exception to be thrown. This is nothing really bad, but you can (and should) prevent that exception by calling SwipeDismissList.discardUndo() in the onStop method of your activity.

If you have implemented the discard method (see above) you MUST call this method.

Contact

For other questions or help, you can find contact data on my page or you will find me often in #android-dev on irc.freenode.net.