1. 程式人生 > >android開發遊記:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView

android開發遊記:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView

obj 長按 lean idle 處理 android開發 touch key gen

以下是RecyclerView結合ItemTouchHelper實現的列表和網格布局的拖拽效果。

效果圖例如以下:(gif圖有點頓卡,事實上執行是非常流暢的)

技術分享圖片 技術分享圖片

demo下載地址:
DragRecyclerView

怎樣實現

那麽是怎樣實現的呢?主要就要使用到ItemTouchHelper ,ItemTouchHelper 是support-v7包中加入的一個幫助開發人員處理拖拽和滑動的實現類,它能夠讓你非常easy實現側滑刪除、拖拽的功能。

我們僅僅須要實例化一個ItemTouchHelper。然後關聯到RecyclerView就OK了:

itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback());
itemTouchHelper.attachToRecyclerView(recyclerView);

構造方法中須要一個ItemTouchHelper.Callback。ItemTouchHelper會在拖拽或剔除的時候回調Callback中對應的方法,我們僅僅需在Callback中實現自己的邏輯就能夠了。

自己定義一個類繼承實現ItemTouchHelper.Callback接口,須要實現以下方法:

     @Override
    public
int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int
direction) { }

getMovementFlags用於設置是否處理拖拽事件和滑動事件。以及拖拽和滑動操作的方向,比方假設是列表類型的RecyclerView,拖拽僅僅有UP、DOWN兩個方向,而假設是網格類型的則有UP、DOWN、LEFT、RIGHT四個方向:

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            final int swipeFlags = 0;
        } else {
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            final int swipeFlags = 0; 
        }
        return makeMovementFlags(dragFlags, swipeFlags);
    }

dragFlags 是拖拽標誌,swipeFlags是滑動標誌,我們把swipeFlags 都設置為0,表示不處理滑動操作。

假設我們設置了非0的dragFlags 。那麽當我們長按item的時候就會進入拖拽並在拖拽過程中不斷回調onMove()方法,我們就在這種方法裏獲取當前拖拽的item和已經被拖拽到所處位置的item的ViewHolder。有了這2個ViewHolder。我們就能夠交換他們的數據集並調用Adapter的notifyItemMoved方法來刷新item。

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
      int fromPosition = viewHolder.getAdapterPosition();//得到拖動ViewHolder的position
      int toPosition = target.getAdapterPosition();//得到目標ViewHolder的position
      if (fromPosition < toPosition) {
          for (int i = fromPosition; i < toPosition; i++) {
              Collections.swap(results, i, i + 1);
          }
      } else {
          for (int i = fromPosition; i > toPosition; i--) {
              Collections.swap(results, i, i - 1);
          }
      }
      adapter.notifyItemMoved(fromPosition, toPosition);
      return true;
}

同理假設我們設置了非0的swipeFlags。我們在滑動item的時候就會回調onSwiped的方法,我們不處理這個事件,空著即可了。

到這裏。已經能夠拖拽了。可是拖拽的時候我們拖拽的對象不能高亮顯示。這是不友好的。我們希望拖拽的Item在拖拽的過程中背景顏色加深,這樣就須要繼續重寫以下兩個方法:

    //當長按選中item的時候(拖拽開始的時候)調用
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
    }

    //當手指松開的時候(拖拽完畢的時候)調用
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    }

我們在開始拖拽的時候給item加入一個背景色,然後在拖拽完畢的時候還原:

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setBackgroundColor(0);
    }

OK,這樣就完畢了一個可拖拽的GridView。

更加復雜的需求

上面的代碼完畢了基本功能,但實際的產品須要往往可能會有些不一樣,比方說,產品希望,有一些item能夠拖拽。一些item無法拖拽,就如上圖的“很多其它”是無法拖拽的。

這個咋辦呢?

事實上在上面我們實現的Callback類中有一個方法我們沒有重寫:

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

這種方法是為了告訴ItemTouchHelper是否須要RecyclerView支持長按拖拽,默認返回是ture(即支持),理所當然我們要支持,所以我們沒有重寫,由於默認true。可是這樣做是默認所有的item都能夠拖拽。怎麽實現部分item拖拽呢,查閱isLongPressDragEnabled方法的源代碼發現。上面的凝視上寫著:

Default value returns true but you may want to disable this if you want to start
dragging on a custom view touch using {@link #startDrag(ViewHolder)}.

意思是假設你想自己定義觸摸view。那麽就使用startDrag(ViewHolder)方法。

原來如此,我們能夠在item的長按事件中得到當前item的ViewHolder 。然後調用ItemTouchHelper.startDrag(ViewHolder vh)就能夠實現拖拽了,那就這麽辦:

首先我們重寫isLongPressDragEnabled返回false,我們要自己調用拖拽過程:

    @Override
    public boolean isLongPressDragEnabled() {
        return false;
    }

接著我們給RecyclerView加入item長按事件。推斷item是否是最後一個(最後一個是“很多其它”),不是則開始拖拽。

可是,我們都知道RecyclerView並沒有提供OnItemLongClickListener。這個問題我在上一篇博客中已經完美地攻克了,就是使用OnItemTouchListener。然後識別觸摸手勢,這裏給上傳送門:RecyclerView無法加入onItemClickListener最佳的高效解決方式,後面我就直接使用上一篇的成果。不反復講了:

recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
    //item 長點擊事件
    @Override
    public void onLongClick(RecyclerView.ViewHolder vh) {
        //假設item不是最後一個,則執行拖拽
        if (vh.getLayoutPosition()!=results.size()-1) {
            itemTouchHelper.startDrag(vh);
        }
    }
    //item 點擊事件
    @Override
    public void onItemClick(RecyclerView.ViewHolder vh) {
    }
});

OK,大功告成。

額外的功能

保存位置

關閉頁面以後再打開,又恢復到了初始化的位置,所以就須要保存調整的位置到本地,下次初始化的時候讀取位置。


保存位置應該由開發人員自己實現。由於每一個人本地化數據的方式都不一樣,我這裏做一個簡單的實現。使用了開源的ACache類,兩個方法,搞定:

//讀取
ACache.get(context).getAsObject("items");
//存儲
ACache.get(context).put("items",results);

在clearView方法(拖拽完畢)中調用存儲方法,在頁面初始化數據是調用讀取方法。

詳見demo

開始拖拽時震動

支付寶的拖拽網格在長按後開始拖拽時會有一次短時間的震動提示用戶開始拖拽了,非常友好的交互。我們也加一個:

加入權限:

<uses-permission android:name="android.permission.VIBRATE" />

在開始拖拽時加入以下代碼:

//獲取系統震動服務
Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE);
//震動70毫秒
vib.vibrate(70);

詳見demo,demo下載地址:

DragRecyclerView

android開發遊記:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView