要實現(xiàn)RecycleView中的拖拽滑動,在以往的經(jīng)驗中經(jīng)常要依賴GestureDetectors、onInterceptTouchEvent等來實現(xiàn),然而在RecyclerView上添加拖動特性有一個非常簡單的方法它就是:ItemTouchHelper。
一、效果圖
以下就是通過RecycleView+ItemTouchHelper實現(xiàn)拖拽滑動的效果圖,看起來有沒有很炫酷。其實實現(xiàn)起來很簡單,我們接下來就開始介紹。

二、ItemTouchHelper的介紹
ItemTouchHelper是一個強大的工具,它處理好了關(guān)于在RecyclerView上添加拖動排序與滑動刪除的所有事情。它是RecyclerView.ItemDecoration的子類,也就是說它可以輕易的添加到幾乎所有的LayoutManager和Adapter中。它還可以和現(xiàn)有的item動畫一起工作,提供受類型限制的拖放動畫等等。
1.添加依賴
compile 'com.android.support:recyclerview-v7:25.1.0'
因為要使用RecycleView,同時ItemTouchHelper也是RecycleView中的類。
2.自定義ItemTouchHelper.Callback類
為了使用ItemTouchHelper,你需要實現(xiàn)ItemTouchHelper.Callback接口,通過這個接口,你可以監(jiān)聽“move”和 “swipe”事件,在這里你也可以控制View的選擇狀態(tài)和重寫默認動畫。
必須實現(xiàn)主要的回調(diào)方法:
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)創(chuàng)建代表方向的Flag。這里我們同時支持drag和swipe。實現(xiàn)這個方法,ItemTouchHelper可以只能drag而不能swipe(反之亦然),總之根據(jù)自己的需求指定。
onMove(RecyclerView, ViewHolder, ViewHolder)
onSwiped(ViewHolder, int)
當(dāng)Item移動或者滑動時,會回調(diào)這兩個方法,然后可以在這兩個方法內(nèi)部設(shè)置回調(diào)通知更新適配器或者頁面顯示的數(shù)據(jù)。
我們還將使用2個幫助方法:
@Override
public boolean isLongPressDragEnabled() {
return true;
}
實現(xiàn)isLongPressDragEnabled()方法返回true去支持長按RecyclerView的item時的drag事件?;蛘?,也可以調(diào)用ItemTouchHelper.startDrag(RecyclerView.ViewHolder) 方法來開始一個拖動。
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
實現(xiàn)isItemViewSwipeEnabled()方法返回true開啟觸摸視圖時的swipe功能。另外ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)也開始swipe事件。
設(shè)置給RecycleView:
實現(xiàn)了以上的方法后,就會監(jiān)聽到拖拽和滑動的手勢,并會處理相關(guān)操作。
接下來需要做的就是把實現(xiàn)的自定義ItemTouchHelper.Callback類設(shè)置給RecycleView。
ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(mNewsChannelMineRv);
以上就是關(guān)于RecycleView+ItemTouchHelper實現(xiàn)拖拽滑動的簡單介紹,下面為實現(xiàn)上述效果圖,具體講解其實現(xiàn)過程。
三、RecycleView+ItemTouchHelper實現(xiàn)拖拽的實例應(yīng)用
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來分別實現(xiàn)我的頻道和更多頻道的內(nèi)容,其實可以使用一個RecyclerView通過判斷類型來實現(xiàn)多布局類型,效果會更好,后續(xù)會嘗試,暫時先這樣了。感興趣的可以參考我的另一篇文章來自行實現(xiàn)。
2.點擊Item增刪效果的實現(xiàn)
先來個簡單的,就是點擊Item后,我的頻道和更多頻道中,一個頻道刪除點擊的頻道,另一個頻道增加該頻道。
思路:設(shè)置Item的點擊事件的監(jiān)聽
在Adaper類中,設(shè)置監(jiān)聽接口,并提供傳入接口對象的方法讓Activity調(diào)用
//Item點擊事件的監(jiān)聽接口
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
當(dāng)點擊Item后,出發(fā)監(jiān)聽,產(chǎn)生回調(diào)
if (mOnItemClickListener != null) {
holder.mLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!table.getNewsChannelFixed()) {
//對項目點擊后增刪操作的監(jiān)聽
mOnItemClickListener.onItemClick(view, holder.getLayoutPosition());
}
}
});
}
在Activity中根據(jù)傳入的數(shù)據(jù)進行操作
我的頻道所對應(yīng)的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());
}
});
更多頻道所對應(yīng)的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());
}
主要實現(xiàn)兩個內(nèi)容:
第一:RecyclerView中內(nèi)容數(shù)據(jù)的修改更新,呈現(xiàn)點擊后增刪的效果。
第二:調(diào)用方法通知進行緩存處理,記錄修改后的效果。同時通知新聞首頁中頻道的更新顯示。關(guān)于第二部分的內(nèi)容不詳細介紹了,可以看最下方的源碼地址。
3.自定義ItemTouchHelper.Callback類實現(xiàn)拖拽效果
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//根據(jù)recyclerView的布局,進行設(shè)置拖拽的方向
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;
}
先根據(jù)布局判斷支持的拖拽的方向,如果是GridLayoutManager 和StaggeredGridLayoutManager支持上下左右拖拽,如果是LinearLayoutManager支持上下拖拽,本例中不支持滑動操作。
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return mOnItemMoveListener.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
}
監(jiān)聽移動事件,其中mOnItemMoveListener.onItemMove是對移動的監(jiān)聽的回調(diào),判斷是否可以移動并通知Adapter類數(shù)據(jù)更新。
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
if (isChannelFixed(fromPosition, toPosition)) {
return false;
}
//在我的頻道中進行子頻道的移動
Collections.swap(getAdapterData(), fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
//通知順序變換,存儲,設(shè)置頻道順序,以及顯示的順序
System.out.println("發(fā)送移動的消息");
EventBus.getDefault().post(new ChannelBean(getAdapterData()));
return true;
}
//不能移動頭條
private boolean isChannelFixed(int fromPosition, int toPosition) {
return fromPosition == 0 || toPosition == 0;
}
兩種情況:
第一:如果移動的是“頭條”頻道或者移動到“頭條”頻道,返回false,則不能進行移動。
第二:不是上面的情況。更新我的頻道欄目中頻道的顯示順序,同時通知數(shù)據(jù)緩存并通知新聞首頁頻道順序的更新。關(guān)于這部分的詳細內(nèi)容,可以看最下方的源碼。
//返回true 允許拖拽
@Override
public boolean isLongPressDragEnabled() {
return mIsLongPressEnabled;
}
是否允許拖拽,通過外部傳入來開啟。
public void setLongPressEnabled(boolean longPressEnabled) {
mIsLongPressEnabled = longPressEnabled;
}
在Adapter類中,根據(jù)觸摸的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.設(shè)置給RecycleView
//Adapter類實現(xiàn)了OnItemMoveListener的接口,將其傳入ItemDragHelperCallback方便接口回調(diào)
ItemDragHelperCallback callback = new ItemDragHelperCallback(mMineAdapter);
//將自定義的ItemDragHelperCallback類傳給ItemTouchHelper
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
//將ItemTouchHelper設(shè)置給RecyclerView
touchHelper.attachToRecyclerView(mNewsChannelMineRv);
通過以上步驟就可以實現(xiàn)效果圖中的效果,真實效果還是不錯的。源碼地址,感興趣的看一下,給個Star支持下,看項目中的NewsChannelActivity部分即可。