类似支付宝应用管理界面——RecycleView+ItemTou
要实现RecycleView中的拖拽滑动,在以往的经验中经常要依赖GestureDetectors、onInterceptTouchEvent等来实现,然而在RecyclerView上添加拖动特性有一个非常简单的方法它就是:ItemTouchHelper。
一、效果图
以下就是通过RecycleView+ItemTouchHelper实现拖拽滑动的效果图,看起来有没有很炫酷。其实实现起来很简单,我们接下来就开始介绍。
二、ItemTouchHelper的介绍
ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情。它是RecyclerView.ItemDecoration的子类,也就是说它可以轻易的添加到几乎所有的LayoutManager和Adapter中。它还可以和现有的item动画一起工作,提供受类型限制的拖放动画等等。
1.添加依赖
compile 'com.android.support:recyclerview-v7:25.1.0'
因为要使用RecycleView,同时ItemTouchHelper也是RecycleView中的类。
2.自定义ItemTouchHelper.Callback类
为了使用ItemTouchHelper,你需要实现ItemTouchHelper.Callback接口,通过这个接口,你可以监听“move”和 “swipe”事件,在这里你也可以控制View的选择状态和重写默认动画。
必须实现主要的回调方法:
getMovementFlags(RecyclerView, ViewHolder)
onMove(RecyclerView, ViewHolder, ViewHolder)
onSwiped(ViewHolder, int)
具体解释这三个方法:
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)创建代表方向的Flag。这里我们同时支持drag和swipe。实现这个方法,ItemTouchHelper可以只能drag而不能swipe(反之亦然),总之根据自己的需求指定。
onMove(RecyclerView, ViewHolder, ViewHolder)
onSwiped(ViewHolder, int)
当Item移动或者滑动时,会回调这两个方法,然后可以在这两个方法内部设置回调通知更新适配器或者页面显示的数据。
我们还将使用2个帮助方法:
@Override
public boolean isLongPressDragEnabled() {
return true;
}
实现isLongPressDragEnabled()方法返回true去支持长按RecyclerView的item时的drag事件。或者,也可以调用ItemTouchHelper.startDrag(RecyclerView.ViewHolder) 方法来开始一个拖动。
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
实现isItemViewSwipeEnabled()方法返回true开启触摸视图时的swipe功能。另外ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)也开始swipe事件。
设置给RecycleView:
实现了以上的方法后,就会监听到拖拽和滑动的手势,并会处理相关操作。
接下来需要做的就是把实现的自定义ItemTouchHelper.Callback类设置给RecycleView。
ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(mNewsChannelMineRv);
以上就是关于RecycleView+ItemTouchHelper实现拖拽滑动的简单介绍,下面为实现上述效果图,具体讲解其实现过程。
三、RecycleView+ItemTouchHelper实现拖拽的实例应用
1.布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:clipToPadding="true"
android:fitsSystemWindows="true"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/action_bar"
android:background="@color/colorPrimary"
app:navigationIcon="@drawable/ic_arrow_back"
app:theme="@style/AppTheme.PopupOverlay"
app:title="@string/channel_manage" />
<TextView
style="@style/news_channel_sort_title"
android:text="我的频道 长按并拖拽可排序" />
<android.support.v7.widget.RecyclerView
android:id="@+id/news_channel_mine_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"></android.support.v7.widget.RecyclerView>
<TextView
style="@style/news_channel_sort_title"
android:text="@string/更多频道 点击添加" />
<android.support.v7.widget.RecyclerView
android:id="@+id/news_channel_more_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"></android.support.v7.widget.RecyclerView>
</LinearLayout>
一共用了两个RecyclerView来分别实现我的频道和更多频道的内容,其实可以使用一个RecyclerView通过判断类型来实现多布局类型,效果会更好,后续会尝试,暂时先这样了。感兴趣的可以参考我的另一篇文章来自行实现。
2.点击Item增删效果的实现
先来个简单的,就是点击Item后,我的频道和更多频道中,一个频道删除点击的频道,另一个频道增加该频道。
思路:设置Item的点击事件的监听
在Adaper类中,设置监听接口,并提供传入接口对象的方法让Activity调用
//Item点击事件的监听接口
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
当点击Item后,出发监听,产生回调
if (mOnItemClickListener != null) {
holder.mLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!table.getNewsChannelFixed()) {
//对项目点击后增删操作的监听
mOnItemClickListener.onItemClick(view, holder.getLayoutPosition());
}
}
});
}
在Activity中根据传入的数据进行操作
我的频道所对应的RecyclerView的操作
mMineAdapter.setOnItemClickListener(new NewsChannelAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
NewsChannelTable newsChannel = mMineAdapter.getAdapterData().get(position);
mMoreAdapter.getAdapterData().add(newsChannel);
mMoreAdapter.notifyDataSetChanged();
mMineAdapter.getAdapterData().remove(position);
mMineAdapter.notifyDataSetChanged();
//进行添加或删除操作后,要更新的列表 进行存储
mPresenter.onItemAddOrRemove((ArrayList<NewsChannelTable>) mMineAdapter.getAdapterData(), (ArrayList<NewsChannelTable>) mMoreAdapter.getAdapterData());
}
});
更多频道所对应的RecyclerView的操作
mMoreAdapter.setOnItemClickListener(new NewsChannelAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
if(mMineAdapter.getAdapterData().size()==7){
ToastUitl.showShort("最多只能添加7个");
}else{
NewsChannelTable newsChannel = mMoreAdapter.getAdapterData().get(position);
mMoreAdapter.getAdapterData().remove(position);
mMoreAdapter.notifyDataSetChanged();
mMineAdapter.getAdapterData().add(newsChannel);
mMineAdapter.notifyDataSetChanged();
List<NewsChannelTable> data = mMineAdapter.getAdapterData();
for (NewsChannelTable table : data) {
System.out.println(table);
}
//进行添加或删除操作后,要更新的列表 进行存储
mPresenter.onItemAddOrRemove((ArrayList<NewsChannelTable>) mMineAdapter.getAdapterData(), (ArrayList<NewsChannelTable>) mMoreAdapter.getAdapterData());
}
主要实现两个内容:
第一:RecyclerView中内容数据的修改更新,呈现点击后增删的效果。
第二:调用方法通知进行缓存处理,记录修改后的效果。同时通知新闻首页中频道的更新显示。关于第二部分的内容不详细介绍了,可以看最下方的源码地址。
3.自定义ItemTouchHelper.Callback类实现拖拽效果
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//根据recyclerView的布局,进行设置拖拽的方向
int dragFlags = setDragFlags(recyclerView);
//不允许进行滑动
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
private int setDragFlags(RecyclerView recyclerView) {
int dragFlags;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager || layoutManager instanceof StaggeredGridLayoutManager) {
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} else {
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
}
return dragFlags;
}
先根据布局判断支持的拖拽的方向,如果是GridLayoutManager 和StaggeredGridLayoutManager支持上下左右拖拽,如果是LinearLayoutManager支持上下拖拽,本例中不支持滑动操作。
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return mOnItemMoveListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
}
监听移动事件,其中mOnItemMoveListener.onItemMove
是对移动的监听的回调,判断是否可以移动并通知Adapter类数据更新。
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
if (isChannelFixed(fromPosition, toPosition)) {
return false;
}
//在我的频道中进行子频道的移动
Collections.swap(getAdapterData(), fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
//通知顺序变换,存储,设置频道顺序,以及显示的顺序
System.out.println("发送移动的消息");
EventBus.getDefault().post(new ChannelBean(getAdapterData()));
return true;
}
//不能移动头条
private boolean isChannelFixed(int fromPosition, int toPosition) {
return fromPosition == 0 || toPosition == 0;
}
两种情况:
第一:如果移动的是“头条”频道或者移动到“头条”频道,返回false,则不能进行移动。
第二:不是上面的情况。更新我的频道栏目中频道的显示顺序,同时通知数据缓存并通知新闻首页频道顺序的更新。关于这部分的详细内容,可以看最下方的源码。
//返回true 允许拖拽
@Override
public boolean isLongPressDragEnabled() {
return mIsLongPressEnabled;
}
是否允许拖拽,通过外部传入来开启。
public void setLongPressEnabled(boolean longPressEnabled) {
mIsLongPressEnabled = longPressEnabled;
}
在Adapter类中,根据触摸的Item的类型来判断是否开启长按拖拽。
if (mItemDragHelperCallback != null) {
holder.mLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mItemDragHelperCallback.setLongPressEnabled(table.getNewsChannelIndex() == 0 ? false : true);
return false;
}
});
}
如果触摸的对象是“头条”频道,则不开启拖拽,其他情况就会开启长按。
4.设置给RecycleView
//Adapter类实现了OnItemMoveListener的接口,将其传入ItemDragHelperCallback方便接口回调
ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
//将自定义的ItemDragHelperCallback类传给ItemTouchHelper
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
//将ItemTouchHelper设置给RecyclerView
touchHelper.attachToRecyclerView(mNewsChannelMineRv);
通过以上步骤就可以实现效果图中的效果,真实效果还是不错的。源码地址,感兴趣的看一下,给个Star支持下,看项目中的NewsChannelActivity部分即可。