RecyclerView進(jìn)階:使用ItemTouchHelper實(shí)現(xiàn)拖拽和側(cè)滑刪除效果

前言

創(chuàng)新互聯(lián)提供網(wǎng)站設(shè)計(jì)制作、網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì),品牌網(wǎng)站設(shè)計(jì),一元廣告等致力于企業(yè)網(wǎng)站建設(shè)與公司網(wǎng)站制作,10年的網(wǎng)站開發(fā)和建站經(jīng)驗(yàn),助力企業(yè)信息化建設(shè),成功案例突破上1000+,是您實(shí)現(xiàn)網(wǎng)站建設(shè)的好選擇.

現(xiàn)在RecyclerView的應(yīng)用越來越廣泛了,不同的應(yīng)用場景需要其作出不同的改變。有時(shí)候我們可能需要實(shí)現(xiàn)側(cè)滑刪除的功能,比如知乎首頁的側(cè)滑刪除,又或者長按Item進(jìn)行拖動(dòng)與其他Item進(jìn)行位置的交換,但RecyclerView沒有提供現(xiàn)成的API供我們操作,所幸SDK提供了ItemTouchHelper這樣一個(gè)工具類幫助我們快速實(shí)現(xiàn)以上功能。不多說別的,我們來介紹一下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是一個(gè)工具類,可實(shí)現(xiàn)側(cè)滑刪除和拖拽移動(dòng),使用這個(gè)工具類需要RecyclerView和Callback。同時(shí)根據(jù)需要重寫onMove和onSwiped方法。接下來就來講述ItemTouchHelper的使用方法。

ItemTouchHelper基本使用方法

step.1新建一個(gè)接口,讓Adapter實(shí)現(xiàn)之

從解耦的角度考慮,我們需要一個(gè)接口來實(shí)現(xiàn)Adapter和ItemTouchHelper之間涉及數(shù)據(jù)的操作,因?yàn)镮temTouchHelper在完成觸摸的各種動(dòng)畫后,就要對Adapter的數(shù)據(jù)進(jìn)行操作,比如側(cè)滑刪除操作,最后需要調(diào)用Adapter的notifyItemRemove()方法來移除該數(shù)據(jù)。因此我們可以把數(shù)據(jù)操作的部分抽象成一個(gè)接口方法,讓ItemTouchHelper.Callback調(diào)用該方法即可。具體如下:

新建ItemTouchHelperAdapter:

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

讓我們的Adapter實(shí)現(xiàn)該接口:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemTouchHelperAdapter {
  //數(shù)據(jù)
  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) {
    //移除數(shù)據(jù)
    mData.remove(position);
    notifyItemRemoved(position);
  }

}

那么我們在ItemTouchHelper.Callback內(nèi)直接調(diào)用接口的方法即可。

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

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

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

例如:要使RecyclerView的Item可以上下拖動(dòng),同時(shí)允許從右到左側(cè)滑,但不許允許從左到右的側(cè)滑,我們可以這樣寫:

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

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

當(dāng)用戶拖動(dòng)一個(gè)Item進(jìn)行上下移動(dòng)從舊的位置到新的位置的時(shí)候會調(diào)用該方法,在該方法內(nèi),我們可以調(diào)用Adapter的notifyItemMoved方法來交換兩個(gè)ViewHolder的位置,最后返回true,表示被拖動(dòng)的ViewHolder已經(jīng)移動(dòng)到了目的位置。所以,如果要實(shí)現(xiàn)拖動(dòng)交換位置,可以重寫該方法(前提是支持上下拖動(dòng)):

  @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)

當(dāng)用戶左右滑動(dòng)Item達(dá)到刪除條件時(shí),會調(diào)用該方法,一般手指觸摸滑動(dòng)的距離達(dá)到RecyclerView寬度的一半時(shí),再松開手指,此時(shí)該Item會繼續(xù)向原先滑動(dòng)方向滑過去并且調(diào)用onSwiped方法進(jìn)行刪除,否則會反向滑回原來的位置。在該方法內(nèi)部我們可以這樣寫:

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

如果在onSwiped方法內(nèi)我們沒有進(jìn)行任何操作,即不刪除已經(jīng)滑過去的Item,那么就會留下空白的地方,因?yàn)閷?shí)際上該ItemView還占據(jù)著該位置,只是移出了我們的可視范圍內(nèi)罷了。

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

5、public boolean boolean isItemViewSwipeEnabled():該方法返回true時(shí),表示如果用戶觸摸并左右滑動(dòng)了View,那么可以執(zhí)行滑動(dòng)刪除操作,即可以調(diào)用到onSwiped()方法。默認(rèn)是返回true。

6、public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState):從靜止?fàn)顟B(tài)變?yōu)橥献Щ蛘呋瑒?dòng)的時(shí)候會回調(diào)該方法,參數(shù)actionState表示當(dāng)前的狀態(tài)。

7、public void clearView(RecyclerView recyclerView, ViewHolder viewHolder):當(dāng)用戶操作完畢某個(gè)item并且其動(dòng)畫也結(jié)束后會調(diào)用該方法,一般我們在該方法內(nèi)恢復(fù)ItemView的初始狀態(tài),防止由于復(fù)用而產(chǎn)生的顯示錯(cuò)亂問題。

8、public void onChildDraw(…):我們可以在這個(gè)方法內(nèi)實(shí)現(xiàn)我們自定義的交互規(guī)則或者自定義的動(dòng)畫效果。
那么完整的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:

  //先實(shí)例化Callback
  ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(myAdapter);
  //用Callback構(gòu)造ItemtouchHelper
  ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
  //調(diào)用ItemTouchHelper的attachToRecyclerView方法建立聯(lián)系
  touchHelper.attachToRecyclerView(mRecyclerView);

經(jīng)過以上步驟,我們已經(jīng)實(shí)現(xiàn)了Item的拖拽和側(cè)滑刪除功能了,看一下效果:

RecyclerView進(jìn)階:使用ItemTouchHelper實(shí)現(xiàn)拖拽和側(cè)滑刪除效果

自定義側(cè)滑動(dòng)畫

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

RecyclerView進(jìn)階:使用ItemTouchHelper實(shí)現(xiàn)拖拽和側(cè)滑刪除效果 

該效果是比較常見的,用戶向左滑動(dòng)Item的時(shí)候,一開始提示的是“左滑刪除”,滑動(dòng)到一定距離后,顯示刪除的圖標(biāo),并且隨著滑動(dòng)距離的增加該圖標(biāo)不斷變大,達(dá)到最大后用戶松開手指,該Item被刪除。

接下來我們來分析一下怎樣實(shí)現(xiàn)以上的效果:

首先,要想左滑出現(xiàn)一個(gè)刪除的方塊,可以在LinearLayout放一個(gè)這樣的“方塊”,讓它與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>

布局文件修改后,我們嘗試來滑動(dòng)一下,發(fā)現(xiàn)后面的刪除方塊并不會出現(xiàn),這是因?yàn)槟J(rèn)的滑動(dòng)方式是setTranslationX(int),即是對整個(gè)View的滑動(dòng),所以無論我們怎樣滑動(dòng),都不會出現(xiàn)刪除方塊。因此,我們要改變一個(gè)種滑動(dòng)方式,比如使用scrollTo(int,int),這種是對View的內(nèi)容的滑動(dòng),所以隨著左滑,item會向左滑去,而位于右方的方塊自然也就出現(xiàn)了。

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

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

最后我們來看看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);
    //重置改變,防止由于復(fù)用而導(dǎo)致的顯示問題
    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) {
    //僅對側(cè)滑狀態(tài)下的效果做出改變
    if (actionState ==ItemTouchHelper.ACTION_STATE_SWIPE){
      //如果dX小于等于刪除方塊的寬度,那么我們把該方塊滑出來
      if (Math.abs(dX) <= getSlideLimitation(viewHolder)){
        viewHolder.itemView.scrollTo(-(int) dX,0);
      }
      //如果dX還未達(dá)到能刪除的距離,此時(shí)慢慢增加“眼睛”的大小,增加的最大值為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 {
      //拖拽狀態(tài)下不做改變,需要調(diào)用父類的方法
      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;
  }
}

好了,到目前為止,自定義效果介紹完畢,讀者可以根據(jù)需求來實(shí)現(xiàn)多樣化的效果。

文章題目:RecyclerView進(jìn)階:使用ItemTouchHelper實(shí)現(xiàn)拖拽和側(cè)滑刪除效果
文章網(wǎng)址:http://muchs.cn/article12/jchdgc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供云服務(wù)器、微信小程序、營銷型網(wǎng)站建設(shè)、定制開發(fā)網(wǎng)站設(shè)計(jì)公司、虛擬主機(jī)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)