android RecyclerView實(shí)現(xiàn)條目Item拖拽排序與滑動(dòng)刪除
效果演示

需求和技術(shù)分析
- RecyclerView Item拖拽排序::長(zhǎng)按RecyclerView的Item或者觸摸Item的某個(gè)按鈕。
- RecyclerView Item滑動(dòng)刪除:RecyclerView Item滑動(dòng)刪除:RecyclerView的Item滑動(dòng)刪除。
實(shí)現(xiàn)方案與技術(shù)
利用ItemTouchHelper綁定RecyclerView、ItemTouchHelper.Callback來(lái)實(shí)現(xiàn)UI更新,并且實(shí)現(xiàn)動(dòng)態(tài)控制是否開(kāi)啟拖拽功能和滑動(dòng)刪除功能。
實(shí)現(xiàn)步驟
- 繼承抽象類ItemTouchHelper,并在構(gòu)造方法傳入實(shí)現(xiàn)的ItemTouchHelper.Callback。
- recyclerView綁定ItemTouchHelper:itemTouchHelper.attachToRecyclerView(recyclerView)。
- 自定義ItemTouchHelper.Callback的實(shí)現(xiàn)接口OnItemTouchCallbackListener,由外部更新RecyclerView的Item。
幾個(gè)主要的布局
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
這個(gè)沒(méi)啥好說(shuō)的了吧,就是一個(gè)RecyclerView啦。
接下來(lái)是RecyclerView的Item的布局item.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeight"
android:background="?selectableItemBackground">
<ImageView
android:id="@+id/iv_touch"
style="@style/ItemStyle"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:src="@android:drawable/alert_dark_frame" />
<CheckBox
android:id="@+id/cb_item_check"
style="@style/ItemStyle"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/tv_name"
style="@style/ItemStyle"
android:layout_toEndOf="@id/cb_item_check"
android:layout_toRightOf="@id/cb_item_check" />
<TextView
android:id="@+id/tv_sex"
style="@style/ItemStyle"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginStart="@dimen/dp_10"
android:layout_toEndOf="@id/tv_name"
android:layout_toRightOf="@id/tv_name" />
</RelativeLayout>
這個(gè)也不用解釋了,到時(shí)候下載看源碼,就是普通item,展示數(shù)據(jù)而已。
實(shí)現(xiàn)自己的DefaultItemTouchHelper:繼承ItemTouchHelper
public class DefaultItemTouchHelper extends ItemTouchHelper {
public DefaultItemTouchHelper(ItemTouchHelp.Callback callback) {
super(callback);
}
}
好嘛,這個(gè)太簡(jiǎn)單了,基本上一行代碼都不用寫。但是這里需要一個(gè)ItemTouchHelp.Callback啊,所以我們還是要實(shí)現(xiàn)一個(gè)ItemTouchHelp.Callback,客觀且看下文分解。
實(shí)現(xiàn)自己的ItemTouchHelper.Callback:繼承ItemTouchHelper.Callback
這里是全文最重要的部分啦,要認(rèn)真點(diǎn)看噢,先上代碼,后解釋,其他看注釋和視頻。
public class DefaultItemTouchHelpCallback extends ItemTouchHelper.Callback {
/**
* Item操作的回調(diào)
*/
private OnItemTouchCallbackListener onItemTouchCallbackListener;
/**
* 是否可以拖拽
*/
private boolean isCanDrag = false;
/**
* 是否可以被滑動(dòng)
*/
private boolean isCanSwipe = false;
public DefaultItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
this.onItemTouchCallbackListener = onItemTouchCallbackListener;
}
/**
* 設(shè)置Item操作的回調(diào),去更新UI和數(shù)據(jù)源
*
* @param onItemTouchCallbackListener
*/
public void setOnItemTouchCallbackListener(OnItemTouchCallbackListener onItemTouchCallbackListener) {
this.onItemTouchCallbackListener = onItemTouchCallbackListener;
}
/**
* 設(shè)置是否可以被拖拽
*
* @param canDrag 是true,否false
*/
public void setDragEnable(boolean canDrag) {
isCanDrag = canDrag;
}
/**
* 設(shè)置是否可以被滑動(dòng)
*
* @param canSwipe 是true,否false
*/
public void setSwipeEnable(boolean canSwipe) {
isCanSwipe = canSwipe;
}
/**
* 當(dāng)Item被長(zhǎng)按的時(shí)候是否可以被拖拽
*
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return isCanDrag;
}
/**
* Item是否可以被滑動(dòng)(H:左右滑動(dòng),V:上下滑動(dòng))
*
* @return
*/
@Override
public boolean isItemViewSwipeEnabled() {
return isCanSwipe;
}
/**
* 當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向
*
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
// flag如果值是0,相當(dāng)于這個(gè)功能被關(guān)閉
int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlag = 0;
// create make
return makeMovementFlags(dragFlag, swipeFlag);
} else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
int orientation = linearLayoutManager.getOrientation();
int dragFlag = 0;
int swipeFlag = 0;
// 為了方便理解,相當(dāng)于分為橫著的ListView和豎著的ListView
if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是橫向的布局
swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是豎向的布局,相當(dāng)于ListView
dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
return makeMovementFlags(dragFlag, swipeFlag);
}
return 0;
}
/**
* 當(dāng)Item被拖拽的時(shí)候被回調(diào)
*
* @param recyclerView recyclerView
* @param srcViewHolder 拖拽的ViewHolder
* @param targetViewHolder 目的地的viewHolder
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder srcViewHolder, ViewHolder targetViewHolder) {
if (onItemTouchCallbackListener != null) {
return onItemTouchCallbackListener.onMove(srcViewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());
}
return false;
}
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
if (onItemTouchCallbackListener != null) {
onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
}
}
public interface OnItemTouchCallbackListener {
/**
* 當(dāng)某個(gè)Item被滑動(dòng)刪除的時(shí)候
*
* @param adapterPosition item的position
*/
void onSwiped(int adapterPosition);
/**
* 當(dāng)兩個(gè)Item位置互換的時(shí)候被回調(diào)
*
* @param srcPosition 拖拽的item的position
* @param targetPosition 目的地的Item的position
* @return 開(kāi)發(fā)者處理了操作應(yīng)該返回true,開(kāi)發(fā)者沒(méi)有處理就返回false
*/
boolean onMove(int srcPosition, int targetPosition);
}
}
好,其實(shí)上面最重要的就是五個(gè)方法:
/**
* 是否可以長(zhǎng)按拖拽排序。
*/
@Override
public boolean isLongPressDragEnabled() {}
/**
* Item是否可以被滑動(dòng)(H:左右滑動(dòng),V:上下滑動(dòng))
*/
@Override
public boolean isItemViewSwipeEnabled() {}
/**
* 當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}
/**
* 當(dāng)Item被拖拽的時(shí)候被回調(diào)
*/
@Override
public boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}
/**
* 當(dāng)View被滑動(dòng)刪除的時(shí)候
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以,isItemViewSwipeEnabled()是否可以滑動(dòng)刪除,true可以,false不可以;這兩個(gè)方法都是配置是否可以操作的。我們上面的代碼中返回了一個(gè)成員變量值,并且這個(gè)值通過(guò)外部可以修改,所以提供了外部控制的方法。
onMove()當(dāng)Item被拖拽排序移動(dòng)到另一個(gè)Item的位置的時(shí)候被回調(diào),onSwiped()當(dāng)Item被滑動(dòng)刪除到不見(jiàn);這兩個(gè)方法是當(dāng)用戶操作了,來(lái)回調(diào)我們,我們就該去更新UI了。這里我們提供了一個(gè)Listener去通知外部,并且返回出去了必要的值,來(lái)降低代碼耦合度。
getMovementFlags()說(shuō)明一:是當(dāng)用戶拖拽或者滑動(dòng)Item的時(shí)候需要我們告訴系統(tǒng)滑動(dòng)或者拖拽的方向,那我們又知道支持拖拽和滑動(dòng)刪除的無(wú)非就是LinearLayoutManager和GridLayoutManager了,相當(dāng)于我們老早的時(shí)候用的ListView和GridView了。所以我們根據(jù)布局管理器的不同做了響應(yīng)的區(qū)分。
getMovementFlags()說(shuō)明二:其他都好理解,就是這里的return makeMovementFlags(dragFlag, swipeFlag);這句話是最終的返回值,也就是它決定了我們的拖拽或者滑動(dòng)的方法。第一個(gè)參數(shù)是拖拽flag,第二個(gè)是滑動(dòng)的flag。
重新定義DefaultItemTouchHelper
我們記得上面定義了一個(gè)DefaultItemTouchHelper,它的構(gòu)造中需要傳一個(gè)ItemTouchHelper.Callback,既然我們實(shí)現(xiàn)禮了,我們?cè)侔袲efaultItemTouchHelper做個(gè)封裝,使使用者更傻瓜式的調(diào)用。
public class DefaultItemTouchHelper extends YolandaItemTouchHelper {
private DefaultItemTouchHelpCallback itemTouchHelpCallback;
public DefaultItemTouchHelper(DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener) {
super(new DefaultItemTouchHelpCallback(onItemTouchCallbackListener));
itemTouchHelpCallback = (DefaultItemTouchHelpCallback) getCallback();
}
/**
* 設(shè)置是否可以被拖拽
*
* @param canDrag 是true,否false
*/
public void setDragEnable(boolean canDrag) {
itemTouchHelpCallback.setDragEnable(canDrag);
}
/**
* 設(shè)置是否可以被滑動(dòng)
*
* @param canSwipe 是true,否false
*/
public void setSwipeEnable(boolean canSwipe) {
itemTouchHelpCallback.setSwipeEnable(canSwipe);
}
}
現(xiàn)在我們看到已經(jīng)不需要傳ItemTouchHelper.Callback給ItemTouchHelper了,只需要傳我們?cè)贒efaultItemTouchHelpCallback中定義好的OnItemTouchCallbackListener就好了,而且提供了設(shè)置是否可以滑動(dòng)和是否可以拖拽的方法,而OnItemTouchCallbackListener只是通知外部滑動(dòng)了、刪除了,你去更新UI吧。
這里可以有的同學(xué)會(huì)有疑問(wèn),上面原來(lái)不是繼承ItemTouchHelper嗎?這里咋就變成了YolandaItemTouchHelper了呢?因?yàn)槲覀兛吹竭@里多了一句itemTouchHelpCallback = getCallback();,這個(gè)getCallback();這個(gè)方法是沒(méi)有的,是我們?cè)赮olandaItemTouchHelper中自定義的,因?yàn)槲覀兿朐贒efaultItemTouchHelper中提供外部設(shè)置是否可以拖拽和滑動(dòng)刪除的方法,就得拿到這個(gè)Callback,所以我看了下源碼,我們?cè)贗temTouchHelper構(gòu)造中把Callback穿進(jìn)去,它保存的時(shí)候一個(gè)package級(jí)別的成員變量,所以我在Android.support.v7.widget.helper包下新建了一個(gè)YolandaItemTouchHelper類:
public class YolandaItemTouchHelper extends ItemTouchHelper {
public YolandaItemTouchHelper(Callback callback) {
super(callback);
}
public Callback getCallback() {
return mCallback;
}
}
如何投入使用
好扯淡也扯完了,封裝也封裝完了,那么接下來(lái)就來(lái)在Activity中使用下咯:
recyclerView綁定ItemTouchHelper
沒(méi)啥好說(shuō)的用itemTouchHelper.attachToRecyclerView(recyclerView)綁定recyclerView和ItemTouchHelper,并且只是允許拖拽和滑動(dòng)刪除Item:
DefaultItemTouchHelper itemTouchHelper = new DefaultItemTouchHelper(onItemTouchCallbackListener); itemTouchHelper.attachToRecyclerView(recyclerView); itemTouchHelper.setDragEnable(true); itemTouchHelper.setSwipeEnable(true);
看到上面還缺少一個(gè)onItemTouchCallbackListener吧,這個(gè)也比較重要。
使用Callback自定義的OnItemTouchCallbackListener刷新UI
我們?cè)谧远xCallback的時(shí)候不是在onMove()和onSwiped()方法中回調(diào)OnItemTouchCallbackListener去更新UI嗎?這里就是OnItemTouchCallbackListener如何更新UI的操作了,完成這個(gè)操作,那么我們的目的就達(dá)到了:
private DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener = new DefaultItemTouchHelpCallback.OnItemTouchCallbackListener() {
@Override
public void onSwiped(int adapterPosition) {
// 滑動(dòng)刪除的時(shí)候,從數(shù)據(jù)源移除,并刷新這個(gè)Item。
if (userInfoList != null) {
userInfoList.remove(adapterPosition);
mainAdapter.notifyItemRemoved(adapterPosition);
}
}
@Override
public boolean onMove(int srcPosition, int targetPosition) {
if (userInfoList != null) {
// 更換數(shù)據(jù)源中的數(shù)據(jù)Item的位置
Collections.swap(userInfoList, srcPosition, targetPosition);
// 更新UI中的Item的位置,主要是給用戶看到交互效果
mainAdapter.notifyItemMoved(srcPosition, targetPosition);
return true;
}
return false;
}
};
到這里就結(jié)束了,不信你去試試,源碼傳送門。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)
這篇文章主要介紹了Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android實(shí)現(xiàn)手機(jī)震動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手機(jī)震動(dòng)效果的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
解決android studio中使用monitor工具無(wú)法打開(kāi)data文件夾問(wèn)題
這篇文章主要介紹了解決android studio中使用monitor工具無(wú)法打開(kāi)data文件夾問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android滑動(dòng)優(yōu)化高仿QQ6.0側(cè)滑菜單(滑動(dòng)優(yōu)化)
之前的實(shí)現(xiàn)只是簡(jiǎn)單的可以顯示和隱藏左側(cè)的菜單,但是特別生硬,而且沒(méi)有任何平滑的趨勢(shì),那么今天就來(lái)優(yōu)化一下吧,加上平滑效果,而且可以根據(jù)手勢(shì)滑動(dòng)的方向來(lái)判斷是否是顯示和隱藏2016-02-02
Android中使用Vectors(2)繪制優(yōu)美的路徑動(dòng)畫(huà)
這篇文章主要介紹了Android中使用Vectors(2)繪制優(yōu)美的路徑動(dòng)畫(huà)的相關(guān)資料,需要的朋友可以參考下2016-03-03
Android?換膚實(shí)現(xiàn)指南demo及案例解析
這篇文章主要為大家介紹了Android換膚指南demo及案例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06

