RecyclerView的拖放和滑动特效
第一部分:基础ItemTouchHelper实例
2015年6月23日
有很多教程,库和实例通过RecyclerView实现Android的“拖放”和“滑动消失”功能。即使有新的,更好的,可用的方法,大多数人仍在使用旧的View.OnDragListener接口和Roman Nurik的SwipeToDismiss做法。极少数人会使用较新的API,但往往采用GestureDetectors和onInterceptTouchEvent接口,或者实现过程较为复杂。实际上,有一个非常简单的途径将这些功能添加到RecyclerView。它只需要一个类,这个类已经存在于Android支持库了:
ItemTouchHelper是一个强大的工具,在实现RecyclerView的“拖放”和“滑动消失”功能时能兼顾到任何细节。它是RecyclerView.ItemDecoration的子类,这意味着它很容易地添加到几乎任何现有的布局管理和适配器中(!)。它还可以适配现有的Item动画,还可以提供类型限制的拖拽,降沉动画,等等。本文我将演示一个基于ItemTouchHelper的简单实现。然后,在这个系列中,我们将范围扩大,去研究更多的特性。
前瞻
如果只是对完整源码感兴趣,您可以跳转到Github上:Android-ItemTouchHelper-Demo.本文即是基于第一次commit提交的代码。从这里下载演示APK。
配置
我们需要的第一件事是配置基本的RecyclerView选项。如果你还没有准备好,更新您的build.gradle文件,包含RecyclerView依赖。
compile “com.android.support:recyclerview-v7:22.2.0”
ItemTouchHelper可以和几乎所有RecyclerView.Adapter适配器和LayoutManager布局管理器交互,但文章将会基于下面的Gist创建基本文件:
https://gist.github.com/iPaulPro/2216ea5e14818056cfcc
使用ItemTouchHelper和ItemTouchHelper.Callback
为了使用ItemTouchHelper,需要先创建一个ItemTouchHelper.Callback接口。这个接口可以监听“移动”和“滑动”事件。您也可以通过它控制选择视图的状态,或者重写默认动画。作为一个辅助类,SimpleCallback回调方法可以为您提供一个基本的实现,但是如果要深入理解它的工作机制,我们需要创建一个自定义的辅助类。
为了实现基本的“拖放”和“滑动消失”功能,以下的方法必须重写:
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()方法来指定哪些拖放和滑动方向是RecyclerView支持的。使用辅助方法ItemTouchHelper.makeMovementFlags(int, int)生成返回标记。在这里我们允许双向的拖放和滑动。
@Override
public boolean isLongPressDragEnabled() {
return true;
}
ItemTouchHelper可用于无滑动的拖拽(反之亦然),所以你必须指定你想要支持哪种方式。要实现长按RecyclerView Item子项激活starting drag(开始拖拽)事件必须从isLongPressDragEnabled()返回true值。
另外,ItemTouchHelper.startDrag(RecyclerView.ViewHolder)方法可以被“handler”调用以开始一个拖拽事件。我们在后文会进一步探讨。
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
为了在视图中任何位置的触摸事件中启用滑动功能,只需从isItemViewSwipeEnabled()方法返回true。另外,通过调用ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)方法可以手动启动一个拖拽事件。
接下来的两个方法,onMove()和onSwiped()需要在方法里通知负责更新基础数据的任何组件。所以首先我们要创建一个接口,使我们能够将这些事件回调传导至事件链。
public interface ItemTouchHelperAdapter {
void onItemMove(int fromPosition, int toPosition);
void onItemDismiss(int position);
}
ItemTouchHelperAdapter.java Gist
对于本例来说,最简单的方法就是让RecyclerListAdapter类实现监听接口。
public class RecyclerListAdapter extends RecyclerView.Adapter<ItemViewHolder
implements ItemTouchHelperAdapter {
// ... code from [gist](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()方法非常重要,这样适配器就能知道Item的变化。同样重要的是要注意,每次改变Item的位置,视图都会形成新的索引,而不是在“拖拽”事件结束时才修改索引。
现在,我们可以创建SimpleItemTouchHelperCallback类,因为我们仍然必须重写onMove()和onSwiped()方法。首先为适配器添加一个构造函数和适一个字段:
private final ItemTouchHelperAdapter mAdapter;
public SimpleItemTouchHelperCallback(
ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}
然后重写剩下的方法以及通知适配器Item的改变。
@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());
}
}
SimpleItemTouchHelperCallback.java hosted with ❤ by GitHub view raw
CallBack类准备好了之后,我们就可以创建ItemTouchHelper类对象以及调用attachToRecyclerView(RecyclerView)方法了(详细请看MainFragment.java):
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
运行程序,效果如下图:
结论
这是ItemTouchHelper辅助类的一个最基础的实现。但是,应该清楚的是,要实现RecylerView基本的“拖放”和“滑动消失”效果是没有必要使用第三方库的。在接下来的部分,我们将研究如何在Item被拖拽或滑动时更好地控制Item的外观。
源码
我在Github上创建了一个项目来展示本系列文章所讲的内容:Android-ItemTouchHelper-Demo。第一次commit的代码对应于以上部分,另外也有一点会在本文第2部分论述。
接下来
第二部分:Handles, Grids, and Custom Animations
Follow me on Google+ and Twitter
© 2015 Paul Burke
All code appearing in this article is licensed under Apache 2.0
Some rights reserved by the author.
TAG : Android App DevelopmentAndroid