RecyclerView默認就有item動畫,例如在增長或刪除item時,都會有一個條目間位移的動畫,但本文要說的不是這個!!!本文的主角是v7包中的ItemTouchHelper,它跟RecyclerView結合後將會帶來神奇的交互效果。示例以下:javascript
效果仍是比較酷炫的吧,上圖中有四步操做:java
下面將一一實現出這些效果android
// 建立ItemTouchHelper,並跟RecyclerView綁定
mItemTouchHelper = new ItemTouchHelper(mCallback);
mItemTouchHelper.attachToRecyclerView(mRv);複製代碼
上面就是ItemTouchHelper在本例中出場的三行代碼中的兩行代碼,但這並不能完成上面的效果,ItemTouchHelper只是一箇中間人,它將ItemTouchHelper.Callback和RecyclerView鏈接起來,具體效果還須要由Callback實現。git
繼承ItemTouchHelper.Callback後必須實現以下三個方法。github
public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return 0;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder targetViewHolder) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
}複製代碼
getMovementFlags()是用來判斷RecyclerView上的哪些方向操做交由ItemTouchHelper.Callback控制,詳細介紹以下:ide
/** * 獲取動做標識 * 動做標識分:dragFlags和swipeFlags * dragFlags:列表滾動方向的動做標識(如豎直列表就是上和下,水平列表就是左和右) * wipeFlags:與列表滾動方向垂直的動做標識(如豎直列表就是左和右,水平列表就是上和下) */
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// 若是你不想上下拖動,能夠將 dragFlags = 0
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
// 若是你不想左右滑動,能夠將 swipeFlags = 0
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
//最終的動做標識(flags)必需要用makeMovementFlags()方法生成
int flags = makeMovementFlags(dragFlags, swipeFlags);
return flags;
}複製代碼
上面我讓item的上下左右都交由ItemTouchHelper.Callback控制,看下效果:佈局
能夠看到左右有效,但上下無效。仔細想一想也是,原本就是豎直滾動列表,若是上下都直接交給ItemTouchHelper.Callback控制了,那RecyclerView的列表滾動功能該怎麼辦?因此,要觸發上下拖動的交互效果,確定有其餘開啓的方式。動畫
直接重寫ItemTouchHelper.Callback的isLongPressDragEnabled()。ui
/** * 是否開啓item長按拖拽功能 */
@Override
public boolean isLongPressDragEnabled() {
return true;
}複製代碼
默認返回是false,重寫返回true。如今看下效果如何:this
長按後能夠拖動了,可是這樣不太方便,接下來實現按住item右圖標進行拖動的效果。
ItemTouchHelper的startDrag(viewHolder)方法能夠手動開啓拖動效果,上面的ItemTouchHelper實例建立在Activity中,而圖標實例在Adapter中,爲了下降耦合,這裏先寫一個接口:
public interface ItemDragListener {
void onStartDrags(RecyclerView.ViewHolder viewHolder);
}複製代碼
接口由Activity實現,在Adapter建立時傳入
public class ItemTouchHelperActivity extends AppCompatActivity implements ItemDragListener {
private ItemTouchHelper mItemTouchHelper;
...
private void setRecyclerView() {
mAdapter = new ItemTouchHelperAdapter(mData, this);
...
}
@Override
public void onStartDrags(RecyclerView.ViewHolder viewHolder) {
mItemTouchHelper.startDrag(viewHolder);
}
}複製代碼
接口由Adapter的圖標觸摸時調用
public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> {
private List<String> mData;
private ItemDragListener mItemDragListener;
public ItemTouchHelperAdapter(List<String> data, ItemDragListener itemDragListener) {
mData = data;
mItemDragListener = itemDragListener;
}
...
@Override
public void onBindViewHolder(final ItemTouchHelperViewHolder viewHolder, int position) {
...
viewHolder.mIvDrag.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mItemDragListener.onStartDrags(viewHolder);
return false;
}
});
}
}複製代碼
這樣在讓圖標在觸摸時,間接的調用了mItemTouchHelper.startDrag(viewHolder),看下效果:
上面作到了item的上下拖動和左右滑動效果,但只是當前item的動畫效果罷了,下面繼續完成與其餘item互動的效果吧。
其實ItemTouchHelper.Callback自己不具有將兩個item互換位置的功能,但RecyclerView能夠,咱們能夠在item拖動的時候把當前item與另外一個item的數據位置交換,再調用RecyclerView的notifyItemMoved()方法刷新佈局,同時,由於RecyclerView自帶item動畫,就能夠完成上面的交互效果了。
item拖動要在ItemTouchHelper.Callback中監聽,而數據交換處理要在Adapter中進行,爲了下降耦合,這裏先寫一個接口:
public interface ItemMoveListener {
boolean onItemMove(int fromPosition, int toPosition);
}複製代碼
接口由Adapter實現
public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> implements ItemMoveListener{
...
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
//一、交換數據
Collections.swap(mData, fromPosition, toPosition);
//二、刷新
notifyItemMoved(fromPosition, toPosition);
return true;
}
}複製代碼
接口在建立ItemTouchHelper.Callback時傳入,在onMove()中調用
public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {
ItemMoveListener mItemMoveListener;
public MyItemTouchHelperCallback(ItemMoveListener itemMoveListener) {
mItemMoveListener = itemMoveListener;
}
...
/** * 當item拖拽移動時觸發 * * @param recyclerView * @param viewHolder 當前被拖拽的item的viewHolder * @param targetViewHolder 當前被拖拽的item下方的另外一個item的viewHolder * @return */
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder targetViewHolder) {
return mItemMoveListener.onItemMove(viewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());
}
}複製代碼
相同的,只要在item滑出屏幕時,將對應的數據刪掉,再調用RecyclerView的notifyItemRemoved()方法刷新佈局便可。
item滑動要在ItemTouchHelper.Callback中監聽,而數據刪除處理要在Adapter中進行,因此只須要加上面的接口中增長一個方法:
public interface ItemMoveListener {
...
boolean onItemRemove(int position);
}複製代碼
接口由Adapter實現
public class ItemTouchHelperAdapter extends RecyclerView.Adapter<ItemTouchHelperAdapter.ItemTouchHelperViewHolder> implements ItemMoveListener{
...
@Override
public boolean onItemRemove(int position) {
//一、刪除數據
mData.remove(position);
//二、刷新
notifyItemRemoved(position);
return true;
}
}複製代碼
接口在ItemTouchHelper.Callback在onSwiped()中調用
/** * 當item側滑出去時觸發(豎直列表是側滑,水平列表是豎滑) * * @param viewHolder * @param direction 滑動的方向 */
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mItemMoveListener.onItemRemove(viewHolder.getAdapterPosition());
}複製代碼
如今大部分效果已經實現,接下來是細節處理。
在item被拖拽或側滑時修改背景色,當動做結束後將背景色恢復回來,而ItemTouchHelper.Callback中正好有對應這兩個狀態的方法,分別是:onSelectedChanged()、clearView()。
/** * 當item被拖拽或側滑時觸發 * * @param viewHolder * @param actionState 當前item的狀態 */
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
//不論是拖拽或是側滑,背景色都要變化
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE)
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(android.R.color.darker_gray));
}
/** * 當item的交互動畫結束時觸發 * * @param recyclerView * @param viewHolder */
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(android.R.color.white));
}複製代碼
在最開始的圖中,當item被側滑出去的過程當中,能夠看到item的透明度在漸漸變淺,高度在漸漸變小,其實就是讓item執行了兩種屬性動畫而已,在ItemTouchHelper.Callback中有一個方法能夠拿到item被拖拽或滑動時的位移變化,那就是onChildDraw()方法,這樣就很好辦了,看以下代碼實現:
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//這句代碼就是item拖拽和滑動效果的實現,因此這句不能省略
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
//咱們只須要在左右滑動時,將透明度和高度的值變小(1 --> 0)
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(value);
viewHolder.itemView.setScaleY(value);
}
}複製代碼
能夠看到,item在滑動過程當中漸漸透明並高度縮小了,可是,我明明是刪除了兩條,怎麼結果列表中多出來兩條空白的數據!這又是爲何呢?
其實上圖中並非多出了兩條空白數據,它們是正常的數據,只是看不到了,這是由於RecyclerView條目(itemView)覆用致使的,前面在onChildDraw()方法中對itemView設置了透明和縮小,而一個列表中固定只有幾個itemView而已,當那兩個透明縮小的itemView被再次使用時,以前設置的透明度和高度比例已是0,因此就出現了這種狀況,解決方法也很簡單,只要在item被移除後,將itemView的透明度和高度比例設置回來便可,代碼以下:
/** * 當item的交互動畫結束時觸發 * * @param recyclerView * @param viewHolder */
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
...
viewHolder.itemView.setAlpha(1);
viewHolder.itemView.setScaleY(1);
}複製代碼
到這裏,最開始的全部交互效果都已經實現了。