ItemTouchHelper
在RecyclerView
的整個體系中,負責監聽Item
的手勢操做,咱們經過給它設置一個繼承於ItemTouchHelper.Callback
的子類,在其中處理Item
的UI
變化,就能夠完成側滑刪除、拖動排序等操做,下面,咱們分如下幾部介紹:android
API
解析API
分析對於Item
的手勢操做分爲兩種:側滑和拖動,若是須要支持這兩種,那麼須要給ItemTouchHelper
傳入一個ItemTouchHelper.Callback
的子類,並把ItemTouchHelper
和RecyclerView
關聯起來,下面,咱們先來介紹一下ItemTouchHelper.Callback
個回調方法的含義:bash
public boolean isLongPressDragEnabled()
是否能夠經過長按來觸發拖動操做,默認返回true
,若是返回false
,那麼能夠經過startDrag(ViewHolder)
方法來觸發某個特定Item
的拖動的機制。public boolean isItemViewSwipeEnabled()
是否能夠對每一個Item
進行側滑。public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
返回對於某個ViewHolder
能夠移動的方向,可選的值有UP/DOWN/LEFT/RIGHT/START/END
。對於縱向排列的線性佈局而言,若是要支持上下拖動排序,那麼就要標誌位中就要包含UP&DOWN
,而若是須要支持左滑刪除,那麼標誌位中就要包含LEFT
。public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target)
當某個被拖動的Item
被從舊位置拖動到了新位置後回調,若是返回true
,那麼ItemTouchHelper
就認爲viewHolder
已經被移動到了target
在Adapter
中的位置。public abstract void onSwiped(ViewHolder viewHolder, int direction)
當某個Item
被滑動到消失時回調,direction
表示滑動的方向。public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
當Item
的狀態發生改變時,回調該方法,actionState
的取值有ACTION_STATE_IDLE/ACTION_STATE_SWIPE/ACTION_STATE_DRAG
。ide
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
標誌着用戶對於某個Item
的操做而且Item
的動畫結束,此時咱們應該恢復它的狀態,以保證它被從新使用的時候能正確地展示。函數
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
佈局
Canvas
:繪製RecyclerView
的Canvas
動畫
dx, dy
:偏移。ui
actionState
:拖拽仍是側滑,對應ACTION_STATE_DRAG
和ACTION_STATE_SWIPE
。this
isCurrentlyActive
爲true
表示這個Item
正在被用戶所控制,false
則表示它僅僅是在回到本來狀態的動畫過程中。spa
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
和上面相似,只不過它是繪製在Item
之上。code
若是咱們但願使用系統默認的效果,那麼只須要作如下幾步:
ItemTouchHelper.Callback
編寫本身的回調類,並在拖動和側滑操做完成以後更新數據: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);
}
}
複製代碼
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
關聯起來,看註釋中的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構造ItemTouchHelper
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchHelper);
//3.把ItemTouchHelper和RecyclerView關聯起來.
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(adapter);
}
複製代碼
下面就是最終的效果:
當咱們須要自定側滑刪除動畫時,那麼須要重寫onChildDraw
或者onChildDrawOver
方法,在其中監聽滑動距離的變化,並根據它來實時改變viewHolder
中的UI
,首先看效果:
Item
的佈局,它包含兩層,頂層是普通狀態的標題文案,而底層則是藍色底的刪除提示:<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
標誌位。onChildDraw
當中,經過傳入的dX
動態改變了普通文案的translationX
,使得底層的刪除提示可以漏出。Adapter
來刪除數據。clearView
中,須要把mTv
重置爲初始的狀態。最後,咱們按照前面的方法,把它和RecyclerView
關聯起來:
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);
}
複製代碼
自定義RecyclerView
的手勢動畫,關鍵是要理解ItemTouchHelper.Callback
中各回調函數的含義,再經過回調函數中傳入的數值來動態改變viewHolder
中保存的itemView
以及其子View
的展示形式,就能夠作出各類絢麗的效果。
RecyclerView 進階:使用 ItemTouchHelper 實現拖拽和側滑刪除