
Существует множество обучающих материалов, библиотек и примеров реализации drag & drop и swipe-to-dismiss в Android c использованием RecyclerView. В большинстве из них по-прежнему используются устаревший View.OnDragListener и подход SwipeToDismiss, разработанный Романом Нуриком. Хотя уже доступны новые и более эффективные методы. Совсем немногие используют новейшие API, зачастую полагаясь на GestureDetectors и onInterceptTouchEvent или же на другие более сложные имплементации. На самом деле существует очень простой способ добавить эти функции в RecyclerView. Для этого требуется всего лишь один класс, который к тому же является частью Android Support Library.
ItemTouchHelper
ItemTouchHelper — это мощная утилита, которая позаботится обо всём, что необходимо сделать, чтобы добавить функции drag & drop и swipe-to-dismiss в RecyclerView. Эта утилита является подклассом RecyclerView.ItemDecoration, благодаря чему её легко добавить практически к любому сущест��ующему LayoutManager и адаптеру. Она также работает с анимацией элементов и предоставляет возможность перетаскивать элементы одного типа на другое место в списке и многое другое. В этой статье я продемонстрирую простую реализацию ItemTouchHelper. Позже, в рамках этой серии статей, мы расширим рамки и рассмотрим остальные возможности.
Примечание. Хотите сразу увидеть результат? Загляните на Github: Android-ItemTouchHelper-Demo. Первый коммит относится к этой статье. Демо .apk-файл можно скачать здесь.

Настройка
Сперва нам нужно настроить RecyclerView. Если вы ещё этого не сделали, добавьте зависимость RecyclerView в свой файл build.gradle.
compile 'com.android.support:recyclerview-v7:22.2.0'ItemTouchHelper будет работать практически с любыми RecyclerView.Adapter и LayoutManager, но эта статья базируется на примерах, использующих эти файлы.
Использование ItemTouchHelper и ItemTouchHelper.Callback
Чтобы использовать ItemTouchHelper, вам необходимо создать ItemTouchHelper.Callback. Это интерфейс, который позволяет отслеживать действия перемещения (англ. move) и смахивания (англ. swipe). Кроме того, здесь вы можете контролировать состояние выделенного view-компонента и переопределять анимацию по умолчанию. Существует вспомогательный класс, который вы можете использовать, если хотите использовать базовую имплементацию, — SimpleCallback. Но для того, чтобы понять, как это работает на практике, сделаем всё самостоятельно.
Основные функции интерфейса, которые мы должны переопределить, чтобы включить базовый функционал drag & drop и swipe-to-dismiss:
getMovementFlags(RecyclerView, ViewHolder)
onMove(RecyclerView, ViewHolder, ViewHolder)
onSwiped(ViewHolder, int)Мы также будем использовать несколько вспомогательных методов:
isLongPressDragEnabled()
isItemViewSwipeEnabled()Рассмотрим их поочередно.
@Override
public int getMovementFlags(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}ItemTouchHelper позволяет легко определить направление события. Вам нужно переопределить метод getMovementFlags(), чтобы указать, какие направления для перетаскивания будут поддерживаться. Для создания возвращаемых флагов используйте вспомогательный метод ItemTouchHelper.makeMovementFlags(int, int). В этом примере мы разрешаем перетаскивание и смахивание в обоих направлениях.
@Override
public boolean isLongPressDragEnabled() {
return true;
}ItemTouchHelper можно использовать только для перетаскивания без функционала смахивания (или наоборот), поэтому вы должны точно указать, какие функции должны поддерживаться. Метод isLongPressDragEnabled() должен возвращать значен��е true, чтобы поддерживалось перетаскивание после длительного нажатия на элемент RecyclerView. В качестве альтернативы можно вызвать метод ItemTouchHelper.startDrag(RecyclerView.ViewHolder), чтобы начать перетаскивание вручную. Рассмотрим этот вариант позже.
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}Чтобы разрешить смахивание после касания где угодно в рамках view-компонента, просто верните значение true из метода isItemViewSwipeEnabled(). В качестве альтернативы можно вызвать метод ItemTouchHelper.startSwipe(RecyclerView.ViewHolder), чтобы начать смахивание вручную.
Следующие два метода, onMove() и onSwiped(), необходимы для того, чтобы уведомить об обновлении данных. Итак, сначала мы создадим интерфейс, который позволит передать эти события по цепочке вызовов.
public interface ItemTouchHelperAdapter {
void onItemMove(int fromPosition, int toPosition);
void onItemDismiss(int position);
}Самый простой способ сделать это — сделать так, чтобы RecyclerListAdapter имплементировал слушателя.
public class RecyclerListAdapter extends
RecyclerView.Adapter<ItemViewHolder>
implements ItemTouchHelperAdapter {
// ... код из [примера](https://gist.github.com/iPaulPro/2216ea5e14818056cfcc#file-recyclerlistadapter-java)
@Override
public void onItemDismiss(int position) {
mItems.remove(position);
notifyItemRemoved(position);
}
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mItems, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(mItems, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
return true;
}Очень важно вызвать методы notifyItemRemoved() и notifyItemMoved(), чтобы адаптер увидел изменения. Также нужно отметить, что мы меняем позицию элемента каждый раз, когда view-компонент смещается на новый индекс, а не в самом конце перемещения (событие «drop»).
Теперь мы можем вернуться к созданию SimpleItemTouchHelperCallback, поскольку нам всё ещё необходимо переопределить методы onMove() и onSwiped(). Сначала добавьте конструктор и поле для адаптера:
private final ItemTouchHelperAdapter mAdapter;
public SimpleItemTouchHelperCallback(
ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}Затем переопределите оставшиеся события и сообщите об этом адаптеру:
@Override
public boolean onMove(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(),
target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder,
int direction) {
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}В результате класс Callback должен выглядеть примерно так:
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final ItemTouchHelperAdapter mAdapter;
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
}Когда Callback готов, мы можем создать ItemTouchHelper и вызвать метод attachToRecyclerView(RecyclerView) (например, в MainFragment.java):
ItemTouchHelper.Callback callback =
new SimpleItemTouchHelperCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);После запуска должно получиться приблизительно следующее:

Заключение
Это максимально упрощённая реализация ItemTouchHelper. Тем не менее, вы можете заметить, что вам не обязательно использовать стороннюю библиотеку для реализации стандартных действий drag & drop и swipe-to-dismiss в RecyclerView. В следующей части мы уделим больше внимания внешнему виду элементов в момент перетаскивания или смахивания.
Исходный код
Я создал проект на GitHub для демонстрации того, о чём рассказывается в этой серии статей: Android-ItemTouchHelper-Demo. Первый коммит в основном относится к этой части и немного ко второй.