Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecyclerView進階:使用ItemTouchHelper實現拖拽和側滑刪除效果

RecyclerView進階:使用ItemTouchHelper實現拖拽和側滑刪除效果

編輯:關於Android編程

前言

現在RecyclerView的應用越來越廣泛了,不同的應用場景需要其作出不同的改變。有時候我們可能需要實現側滑刪除的功能,比如知乎首頁的側滑刪除,又或者長按Item進行拖動與其他Item進行位置的交換,但RecyclerView沒有提供現成的API供我們操作,所幸SDK提供了ItemTouchHelper這樣一個工具類幫助我們快速實現以上功能。不多說別的,我們來介紹一下ItemTouchHelper。

什麼是ItemTouchHelper

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.Depending on which functionality you support, you should override onMove(RecyclerView, ViewHolder, ViewHolder) and / or onSwiped(ViewHolder, int).

以上是官方文檔的介紹,ItemTouchHelper是一個工具類,可實現側滑刪除和拖拽移動,使用這個工具類需要RecyclerView和Callback。同時根據需要重寫onMove和onSwiped方法。接下來就來講述ItemTouchHelper的使用方法。

ItemTouchHelper基本使用方法

step.1新建一個接口,讓Adapter實現之

從解耦的角度考慮,我們需要一個接口來實現Adapter和ItemTouchHelper之間涉及數據的操作,因為ItemTouchHelper在完成觸摸的各種動畫後,就要對Adapter的數據進行操作,比如側滑刪除操作,最後需要調用Adapter的notifyItemRemove()方法來移除該數據。因此我們可以把數據操作的部分抽象成一個接口方法,讓ItemTouchHelper.Callback調用該方法即可。具體如下:

新建ItemTouchHelperAdapter:

public interface ItemTouchHelperAdapter {
  //數據交換
  void onItemMove(int fromPosition,int toPosition);
  //數據刪除
  void onItemDissmiss(int position);
}

讓我們的Adapter實現該接口:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemTouchHelperAdapter {
  //數據
  private List<String> mData;
  ...
   @Override
  public void onItemMove(int fromPosition, int toPosition) {
    //交換位置
    Collections.swap(mData,fromPosition,toPosition);
    notifyItemMoved(fromPosition,toPosition);
  }

  @Override
  public void onItemDissmiss(int position) {
    //移除數據
    mData.remove(position);
    notifyItemRemoved(position);
  }

}

那麼我們在ItemTouchHelper.Callback內直接調用接口的方法即可。

step.2新建類繼承自ItemTouchHelper.Callback

從官方文檔我們知道,使用ItemTouchHelper需要一個Callback,該Callback是ItemTouchHelper.Callback的子類,所以我們需要新建一個類比如SimpleItemTouchHelperCallback繼承自ItemTouchHelper.Callback。我們可以重寫其數個方法來實現我們的需求。我們先來看看ItemTouchHelper.Callback需要重寫的幾個常用的方法。

1、public int getMovementFlags(RecyclerView, RecyclerView.ViewHolder):該方法用於返回可以滑動的方向,比如說允許從右到左側滑,允許上下拖動等。我們一般使用makeMovementFlags(int,int)或makeFlag(int, int)來構造我們的返回值。

例如:要使RecyclerView的Item可以上下拖動,同時允許從右到左側滑,但不許允許從左到右的側滑,我們可以這樣寫:

  @Override
  public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;    //允許上下的拖動
    int swipeFlags = ItemTouchHelper.LEFT;  //只允許從右向左側滑
    return makeMovementFlags(dragFlags,swipeFlags);
  }

2、public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)

當用戶拖動一個Item進行上下移動從舊的位置到新的位置的時候會調用該方法,在該方法內,我們可以調用Adapter的notifyItemMoved方法來交換兩個ViewHolder的位置,最後返回true,表示被拖動的ViewHolder已經移動到了目的位置。所以,如果要實現拖動交換位置,可以重寫該方法(前提是支持上下拖動):

  @Override
  public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    //onItemMove是接口方法
    mAdapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition()); 
    return true;
  }

3、public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction)

當用戶左右滑動Item達到刪除條件時,會調用該方法,一般手指觸摸滑動的距離達到RecyclerView寬度的一半時,再松開手指,此時該Item會繼續向原先滑動方向滑過去並且調用onSwiped方法進行刪除,否則會反向滑回原來的位置。在該方法內部我們可以這樣寫:

  @Override
  public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    //onItemDissmiss是接口方法
    mAdapter.onItemDissmiss(viewHolder.getAdapterPosition());
  }

如果在onSwiped方法內我們沒有進行任何操作,即不刪除已經滑過去的Item,那麼就會留下空白的地方,因為實際上該ItemView還占據著該位置,只是移出了我們的可視范圍內罷了。

4、public boolean isLongPressDragEnabled():該方法返回true時,表示支持長按拖動,即長按ItemView後才可以拖動,我們遇到的場景一般也是這樣的。默認是返回true。

5、public boolean boolean isItemViewSwipeEnabled():該方法返回true時,表示如果用戶觸摸並左右滑動了View,那麼可以執行滑動刪除操作,即可以調用到onSwiped()方法。默認是返回true。

6、public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState):從靜止狀態變為拖拽或者滑動的時候會回調該方法,參數actionState表示當前的狀態。

7、public void clearView(RecyclerView recyclerView, ViewHolder viewHolder):當用戶操作完畢某個item並且其動畫也結束後會調用該方法,一般我們在該方法內恢復ItemView的初始狀態,防止由於復用而產生的顯示錯亂問題。

8、public void onChildDraw(…):我們可以在這個方法內實現我們自定義的交互規則或者自定義的動畫效果。
那麼完整的SimpleItemTouchHelperCallback文件是這樣的:

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback{

  private ItemTouchHelperAdapter mAdapter;

  public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
    mAdapter = adapter;
  }

  @Override
  public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.LEFT;
    return makeMovementFlags(dragFlags,swipeFlags);
  }

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

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

  @Override
  public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    mAdapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
    return true;
  }

  @Override
  public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    mAdapter.onItemDissmiss(viewHolder.getAdapterPosition());
  }
}

step.3為RecycleView添加ItemTouchHelper

上面我們修改了Adapter和新建了ItemTouchHelper.Callback的子類,接下來我們要為RecyclerView添加ItemTouchHelper:

  //先實例化Callback
  ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(myAdapter);
  //用Callback構造ItemtouchHelper
  ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
  //調用ItemTouchHelper的attachToRecyclerView方法建立聯系
  touchHelper.attachToRecyclerView(mRecyclerView);

經過以上步驟,我們已經實現了Item的拖拽和側滑刪除功能了,看一下效果:

拖拽和側滑

自定義側滑動畫

有時候我們對默認的動畫效果可能不滿意,需要自己實現想要的動畫效果,ItemTouchHelper.Callback提供的onChildDraw方法可以讓我們很方便地實現想要的效果。以下帶來一種自定義的實現效果,當做拋磚引玉,讓大家熟悉自定義效果的運用。先來看看要實現的效果:

自定義 

該效果是比較常見的,用戶向左滑動Item的時候,一開始提示的是“左滑刪除”,滑動到一定距離後,顯示刪除的圖標,並且隨著滑動距離的增加該圖標不斷變大,達到最大後用戶松開手指,該Item被刪除。

接下來我們來分析一下怎樣實現以上的效果:

首先,要想左滑出現一個刪除的方塊,可以在LinearLayout放一個這樣的“方塊”,讓它與Item水平並排排列,以下是布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_height="wrap_content"
  android:layout_width="match_parent"
  android:orientation="horizontal">

  <android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="#ffffff"
    android:layout_marginLeft="4dp"
    android:layout_marginRight="4dp"
    android:layout_marginBottom="4dp"
    app:cardCornerRadius="1dp"
    app:elevation="1dp"
    app:contentPadding="1dp">
    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#ffffff">
      <TextView
        android:id="@+id/item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22sp"
        android:padding="4dp"
        android:layout_centerInParent="true"/>

    </RelativeLayout>
  </android.support.v7.widget.CardView>

  <FrameLayout
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:layout_marginRight="4dp"
    android:layout_marginBottom="4dp"
    android:background="#f33213">

    <ImageView
      android:id="@+id/iv_img"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:layout_gravity="center"
      android:src="@mipmap/ic_eye_72"
      android:visibility="invisible"/>
    <TextView
      android:id="@+id/tv_text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="左滑刪除"
      android:textSize="18sp"
      android:textColor="#ffffff"
      android:layout_gravity="center"/>
  </FrameLayout>
</LinearLayout>

布局文件修改後,我們嘗試來滑動一下,發現後面的刪除方塊並不會出現,這是因為默認的滑動方式是setTranslationX(int),即是對整個View的滑動,所以無論我們怎樣滑動,都不會出現刪除方塊。因此,我們要改變一個種滑動方式,比如使用scrollTo(int,int),這種是對View的內容的滑動,所以隨著左滑,item會向左滑去,而位於右方的方塊自然也就出現了。

接著,我們考慮該“刪除眼睛”的圖標是怎樣從小變大的,這個實現也比較簡單,只要根據滑動的距離對該ImageView的LayoutParams.width進行改變就行了,不過要注意限制大小,否則過大會造成圖片的失真。當滑動距離等於RecyclerView寬度的一半時,此時松開手會使Item刪除,那麼我們可以在該滑動距離達到該值時時“眼睛”變得最大,此時可以達到良好的交互效果,提示了用戶無需繼續滑動即可刪除該Item了。

最後我們要考慮的是:在刪除了Item或者沒刪除而滑回原來的位置後,我們要把所做的改變重置一下,否則,會由於RecyclerView的復用而導致其他位置的ViewHolder與當前的ViewHolder所做的改變一樣,即造成顯示的錯誤。我們可以在clearView()方法內重置改變,這樣就能解決因復用而導致的顯示問題了。

最後我們來看看SimpleItemTouchHelperCallback的代碼:

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback{

  //省略上面的代碼....

  //限制ImageView長度所能增加的最大值
  private double ICON_MAX_SIZE = 50;
  //ImageView的初始長寬
  private int fixedWidth = 150;

  @Override
  public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
    //重置改變,防止由於復用而導致的顯示問題
    viewHolder.itemView.setScrollX(0);
    ((MyAdapter.NormalItem)viewHolder).tv.setText("左滑刪除");
    FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) ((MyAdapter.NormalItem) viewHolder).iv.getLayoutParams();
    params.width = 150;
    params.height = 150;
    ((MyAdapter.NormalItem) viewHolder).iv.setLayoutParams(params);
    ((MyAdapter.NormalItem) viewHolder).iv.setVisibility(View.INVISIBLE);
  }

  @Override
  public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    //僅對側滑狀態下的效果做出改變
    if (actionState ==ItemTouchHelper.ACTION_STATE_SWIPE){
      //如果dX小於等於刪除方塊的寬度,那麼我們把該方塊滑出來
      if (Math.abs(dX) <= getSlideLimitation(viewHolder)){
        viewHolder.itemView.scrollTo(-(int) dX,0);
      }
      //如果dX還未達到能刪除的距離,此時慢慢增加“眼睛”的大小,增加的最大值為ICON_MAX_SIZE
      else if (Math.abs(dX) <= recyclerView.getWidth() / 2){
        double distance = (recyclerView.getWidth() / 2 -getSlideLimitation(viewHolder));
        double factor = ICON_MAX_SIZE / distance;
        double diff = (Math.abs(dX) - getSlideLimitation(viewHolder)) * factor;
        if (diff >= ICON_MAX_SIZE)
          diff = ICON_MAX_SIZE;
        ((MyAdapter.NormalItem)viewHolder).tv.setText("");  //把文字去掉
        ((MyAdapter.NormalItem) viewHolder).iv.setVisibility(View.VISIBLE); //顯示眼睛
        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) ((MyAdapter.NormalItem) viewHolder).iv.getLayoutParams();
        params.width = (int) (fixWidth + diff);
        params.height = (int) (fixWidth + diff);
        ((MyAdapter.NormalItem) viewHolder).iv.setLayoutParams(params);
      }
    }else {
      //拖拽狀態下不做改變,需要調用父類的方法
      super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive);
    }
  }

  /**
   * 獲取刪除方塊的寬度
   */
  public int getSlideLimitation(RecyclerView.ViewHolder viewHolder){
    ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
    return viewGroup.getChildAt(1).getLayoutParams().width;
  }
}

好了,到目前為止,自定義效果介紹完畢,讀者可以根據需求來實現多樣化的效果。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved