一、概述
ItemTouchHelper
在RecyclerView
的整個體系中憔四,負(fù)責(zé)監(jiān)聽Item
的手勢操作缎罢,我們通過給它設(shè)置一個繼承于ItemTouchHelper.Callback
的子類,在其中處理Item
的UI
變化窘疮,就可以完成側(cè)滑刪除骑素、拖動排序等操作炫乓,下面,我們分以下幾部介紹:
-
API
解析 - 實戰(zhàn)
- 采用默認(rèn)動畫
- 自定義側(cè)滑刪除動畫
二献丑、API
分析
對于Item
的手勢操作分為兩種:側(cè)滑和拖動末捣,如果需要支持這兩種,那么需要給ItemTouchHelper
傳入一個ItemTouchHelper.Callback
的子類阳距,并把ItemTouchHelper
和RecyclerView
關(guān)聯(lián)起來塔粒,下面结借,我們先來介紹一下ItemTouchHelper.Callback
個回調(diào)方法的含義:
控制相關(guān)
-
public boolean isLongPressDragEnabled()
是否可以通過長按來觸發(fā)拖動操作筐摘,默認(rèn)返回true
,如果返回false
船老,那么可以通過startDrag(ViewHolder)
方法來觸發(fā)某個特定Item
的拖動的機制咖熟。 -
public boolean isItemViewSwipeEnabled()
是否可以對每個Item
進行側(cè)滑。 -
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
返回對于某個ViewHolder
可以移動的方向柳畔,可選的值有UP/DOWN/LEFT/RIGHT/START/END
馍管。對于縱向排列的線性布局而言,如果要支持上下拖動排序薪韩,那么就要標(biāo)志位中就要包含UP&DOWN
确沸,而如果需要支持左滑刪除捌锭,那么標(biāo)志位中就要包含LEFT
。
結(jié)果相關(guān)
-
public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target)
當(dāng)某個被拖動的Item
被從舊位置拖動到了新位置后回調(diào)罗捎,如果返回true
观谦,那么ItemTouchHelper
就認(rèn)為viewHolder
已經(jīng)被移動到了target
在Adapter
中的位置。 -
public abstract void onSwiped(ViewHolder viewHolder, int direction)
當(dāng)某個Item
被滑動到消失時回調(diào)桨菜,direction
表示滑動的方向豁状。
狀態(tài)相關(guān)
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
當(dāng)Item
的狀態(tài)發(fā)生改變時,回調(diào)該方法倒得,actionState
的取值有ACTION_STATE_IDLE/ACTION_STATE_SWIPE/ACTION_STATE_DRAG
泻红。public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
標(biāo)志著用戶對于某個Item
的操作并且Item
的動畫結(jié)束,此時我們應(yīng)該恢復(fù)它的狀態(tài)霞掺,以保證它被重新使用的時候能正確地展現(xiàn)谊路。public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
Canvas
:繪制RecyclerView
的Canvas
dx, dy
:偏移。actionState
:拖拽還是側(cè)滑菩彬,對應(yīng)ACTION_STATE_DRAG
和ACTION_STATE_SWIPE
凶异。isCurrentlyActive
為true
表示這個Item
正在被用戶所控制,false
則表示它僅僅是在回到原本狀態(tài)的動畫過程當(dāng)中挤巡。public void onChildDrawOver(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
和上面類似剩彬,只不過它是繪制在Item
之上。
三矿卑、實戰(zhàn)
3.1 使用系統(tǒng)默認(rèn)效果
如果我們希望使用系統(tǒng)默認(rèn)的效果喉恋,那么只需要做以下幾步:
- 繼承于
ItemTouchHelper.Callback
編寫自己的回調(diào)類,并在拖動和側(cè)滑操作完成之后更新數(shù)據(jù):
public class SimpleItemTouchHelper extends ItemTouchHelper.Callback {
private ItemTouchAdapter mAdapter;
public SimpleItemTouchHelper(ItemTouchAdapter adapter) {
mAdapter = adapter;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
Log.d("SimpleItemTouchHelper", "onSwiped, onMove, source=" + viewHolder.getAdapterPosition() + ",target=" + target.getAdapterPosition());
mAdapter.onItemDragged(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Log.d("SimpleItemTouchHelper", "onSwiped, direction=" + direction);
mAdapter.onItemSwiped(viewHolder.getAdapterPosition());
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
}
- 編寫數(shù)據(jù)操作的代碼:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> implements ItemTouchAdapter {
//......
@Override
public void onItemDragged(int from, int to) {
Collections.swap(mTitles, from, to);
notifyItemMoved(from, to);
}
@Override
public void onItemSwiped(int position) {
mTitles.remove(position);
notifyItemRemoved(position);
}
}
- 把
ItemTouchHelper.Callback
和RecyclerView
關(guān)聯(lián)起來母廷,看注釋中的1,2,3
步:
private void init() {
List<String> titles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
titles.add(String.valueOf(i));
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
recyclerView.setLayoutManager(layoutManager);
NormalAdapter adapter = new NormalAdapter(titles);
//1.自定義的ItemTouchHeloer.Callback
SimpleItemTouchHelper simpleItemTouchHelper = new
SimpleItemTouchHelper(adapter);
//2.利用這個Callback構(gòu)造ItemTouchHelper
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchHelper);
//3.把ItemTouchHelper和RecyclerView關(guān)聯(lián)起來.
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(adapter);
}
下面就是最終的效果:
3.2 自定義側(cè)滑刪除動畫
當(dāng)我們需要自定側(cè)滑刪除動畫時轻黑,那么需要重寫onChildDraw
或者onChildDrawOver
方法,在其中監(jiān)聽滑動距離的變化琴昆,并根據(jù)它來實時改變viewHolder
中的UI
氓鄙,首先看效果:
- 首先,我們需要重寫
Item
的布局业舍,它包含兩層抖拦,頂層是普通狀態(tài)的標(biāo)題文案,而底層則是藍(lán)色底的刪除提示:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="66dp">
<!-- 刪除提示 -->
<LinearLayout
android:id="@+id/ll_delete"
android:orientation="vertical"
android:gravity="center"
android:layout_gravity="end"
android:background="@android:color/holo_blue_dark"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:src="@android:drawable/ic_input_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="delete"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 普通文案 -->
<TextView
android:id="@+id/tv_title"
android:gravity="center"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
接著舷暮,我們需要重寫ItemTouchHelper.Callback
:
public class AdvancedItemTouchHelper extends ItemTouchHelper.Callback {
private ItemTouchAdapter mAdapter;
public AdvancedItemTouchHelper(ItemTouchAdapter itemTouchAdapter) {
mAdapter = itemTouchAdapter;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mAdapter.onItemSwiped(viewHolder.getAdapterPosition());
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
((NormalAdapter.NormalViewHolder) viewHolder).mTv.setTranslationX(0);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
NormalAdapter.NormalViewHolder mViewHolder = (NormalAdapter.NormalViewHolder) viewHolder;
int deleteWidth = mViewHolder.mDeleteLayout.getWidth();
float fraction = deleteWidth / (float) mViewHolder.itemView.getWidth();
mViewHolder.mTv.setTranslationX(dX * fraction);
}
}
這里面有幾點需要注意:
- 為了讓
Item
支持左滑刪除态罪,我們需要在getMovementFlags
中返回ItemTouchHelper.LEFT
標(biāo)志位。 - 在
onChildDraw
當(dāng)中下面,通過傳入的dX
動態(tài)改變了普通文案的translationX
复颈,使得底層的刪除提示能夠漏出。 - 在側(cè)滑操作完成之后沥割,通過
Adapter
來刪除數(shù)據(jù)耗啦。 - 在
clearView
中凿菩,需要把mTv
重置為初始的狀態(tài)。
最后帜讲,我們按照前面的方法蓄髓,把它和RecyclerView
關(guān)聯(lián)起來:
private void init() {
List<String> titles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
titles.add("Item " + String.valueOf(i));
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
recyclerView.setLayoutManager(layoutManager);
NormalAdapter adapter = new NormalAdapter(titles);
AdvancedItemTouchHelper advancedItemTouchHelper = new AdvancedItemTouchHelper(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(advancedItemTouchHelper);
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
四、小結(jié)
自定義RecyclerView
的手勢動畫舒帮,關(guān)鍵是要理解ItemTouchHelper.Callback
中各回調(diào)函數(shù)的含義会喝,再通過回調(diào)函數(shù)中傳入的數(shù)值來動態(tài)改變viewHolder
中保存的itemView
以及其子View
的展現(xiàn)形式,就可以做出各種絢麗的效果玩郊。
五肢执、參考文獻(xiàn)
RecyclerView 進階:使用 ItemTouchHelper 實現(xiàn)拖拽和側(cè)滑刪除
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/