RecyclerView 之使用 ItemTouchHelper 實現交互動畫

1、簡述

RecyclerView默認就有item動畫,例如在增長或刪除item時,都會有一個條目間位移的動畫,但本文要說的不是這個!!!本文的主角是v7包中的ItemTouchHelper,它跟RecyclerView結合後將會帶來神奇的交互效果。示例以下:javascript

效果仍是比較酷炫的吧,上圖中有四步操做:java

  1. 長按item後拖動,與其餘item交換位置
  2. 按住item右面的圖標後拖動,與其餘item交換位置
  3. 左滑item變透明並縮小,超出屏幕後,其餘item補上
  4. 右滑item變透明並縮小,超出屏幕後,其餘item補上

下面將一一實現出這些效果android

2、初識ItemTouchHelper

一、建立ItemTouchHelper

// 建立ItemTouchHelper,並跟RecyclerView綁定
mItemTouchHelper = new ItemTouchHelper(mCallback);
mItemTouchHelper.attachToRecyclerView(mRv);複製代碼

上面就是ItemTouchHelper在本例中出場的三行代碼中的兩行代碼,但這並不能完成上面的效果,ItemTouchHelper只是一箇中間人,它將ItemTouchHelper.Callback和RecyclerView鏈接起來,具體效果還須要由Callback實現。git

二、自定義ItemTouchHelper.Callback

1)建立一個本身的Callback

繼承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) {

    }
}複製代碼

2)重寫getMovementFlags()

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的列表滾動功能該怎麼辦?因此,要觸發上下拖動的交互效果,確定有其餘開啓的方式。動畫

3)開啓長按item拖動效果

直接重寫ItemTouchHelper.Callback的isLongPressDragEnabled()。ui

/** * 是否開啓item長按拖拽功能 */
@Override
public boolean isLongPressDragEnabled() {
    return true;
}複製代碼

默認返回是false,重寫返回true。如今看下效果如何:this

長按後能夠拖動了,可是這樣不太方便,接下來實現按住item右圖標進行拖動的效果。

4)開啓按住圖標拖動效果

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),看下效果:

3、深刻ItemTouchHelper

上面作到了item的上下拖動和左右滑動效果,但只是當前item的動畫效果罷了,下面繼續完成與其餘item互動的效果吧。

一、上下拖動時與其餘item進行位置交換

1)原理

其實ItemTouchHelper.Callback自己不具有將兩個item互換位置的功能,但RecyclerView能夠,咱們能夠在item拖動的時候把當前item與另外一個item的數據位置交換,再調用RecyclerView的notifyItemMoved()方法刷新佈局,同時,由於RecyclerView自帶item動畫,就能夠完成上面的交互效果了。

2)實現

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());
    }
}複製代碼

3)效果:

二、左右滑出屏幕時其餘item補上

1)原理

相同的,只要在item滑出屏幕時,將對應的數據刪掉,再調用RecyclerView的notifyItemRemoved()方法刷新佈局便可。

2)實現

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());
}複製代碼

3)效果:

如今大部分效果已經實現,接下來是細節處理。

三、交互時背景變化

1)原理

在item被拖拽或側滑時修改背景色,當動做結束後將背景色恢復回來,而ItemTouchHelper.Callback中正好有對應這兩個狀態的方法,分別是:onSelectedChanged()、clearView()。

2)實現

/** * 當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));
}複製代碼

3)效果

四、左右滑動時item漸變

1)分析及實現

在最開始的圖中,當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);
    }
}複製代碼

2)效果(有瑕疵)

能夠看到,item在滑動過程當中漸漸透明並高度縮小了,可是,我明明是刪除了兩條,怎麼結果列表中多出來兩條空白的數據!這又是爲何呢?

3)修復

其實上圖中並非多出了兩條空白數據,它們是正常的數據,只是看不到了,這是由於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);
}複製代碼

4)效果(完美)

到這裏,最開始的全部交互效果都已經實現了。

最後附上Demo連接

github.com/GitLqr/Mate…

相關文章
相關標籤/搜索