RecyclerView實(shí)現(xiàn)滑動(dòng)刪除和拖拽功能

源碼傳送門(mén)

前言

從Android 5.0開(kāi)始,谷歌推出了新的控件RecyclerView,相對(duì)于早它之前的ListView鸭你,優(yōu)點(diǎn)多多,功能強(qiáng)大,也給我們的開(kāi)發(fā)著提供了極大的便利袱巨,今天自己學(xué)習(xí)一下RecyclerView輕松實(shí)現(xiàn)滑動(dòng)刪除及拖拽的效果阁谆,如下圖。

這里寫(xiě)圖片描述

相信研究過(guò)RecyclerView的同學(xué)愉老,應(yīng)該很清楚該怎么實(shí)現(xiàn)這樣的效果场绿,若是用ListView,這樣的效果實(shí)現(xiàn)起來(lái)可能就有點(diǎn)麻煩,但是在強(qiáng)大的RecyclerView面前這樣的的效果只需很少的代碼嫉入,因?yàn)楣雀杞o我們提供了強(qiáng)大的工具類(lèi)ItemTouchHelper焰盗,它已經(jīng)處理了關(guān)于RecyclerView拖動(dòng)和滑動(dòng)的實(shí)現(xiàn),并且我們可以在其中實(shí)現(xiàn)我們自己的動(dòng)畫(huà)咒林,以及定制我們想要的效果熬拒。

ItemTouchHelper.Callback

ItemTouchHelper.Callback有幾個(gè)重要的抽象方法,我們繼承該抽象類(lèi)垫竞,并重寫(xiě)抽象方法澎粟。它是我們實(shí)現(xiàn)滑動(dòng)和拖拽重要的回調(diào)。

int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)

該方法返回一個(gè)整數(shù)欢瞪,用來(lái)指定拖拽和滑動(dòng)在哪個(gè)方向是被允許的捌议。在其中使用makeMovementFlags(int dragFlags, int swipeFlags)返回,該方法第一個(gè)參數(shù)用來(lái)指定拖動(dòng)引有,第二個(gè)參數(shù)用來(lái)指定滑動(dòng)。對(duì)于方向參數(shù)有6種


ItemTouchHelper.UP  //滑動(dòng)拖拽向上方向
ItemTouchHelper.DOWN//向下
ItemTouchHelper.LEFT//向左
ItemTouchHelper.RIGHT//向右
ItemTouchHelper.START//依賴(lài)布局方向的水平開(kāi)始方向
ItemTouchHelper.END//依賴(lài)布局方向的水平結(jié)束方向
boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)

onMove方法是拖拽的回調(diào)倦逐,參數(shù)viewHolder是拖動(dòng)的Item譬正,target是拖動(dòng)的目標(biāo)位置的Item,該方法如果返回true表示切換了位置,反之返回false檬姥。

void onSwiped(RecyclerView.ViewHolder viewHolder, int direction)

onSwiped方法為Item滑動(dòng)回調(diào)曾我,viewHolder為滑動(dòng)的item,direction為滑動(dòng)的方向健民。
上面三個(gè)方法是必須重寫(xiě)的方法抒巢,當(dāng)然還有其它一些可供選擇的方法。

    /**
     * Item是否支持長(zhǎng)按拖動(dòng)
     *
     * @return
     *          true  支持長(zhǎng)按操作
     *          false 不支持長(zhǎng)按操作
     */
boolean isLongPressDragEnabled()

    /**
     * Item是否支持滑動(dòng)
     *
     * @return
     *          true  支持滑動(dòng)操作
     *          false 不支持滑動(dòng)操作
     */
boolean isItemViewSwipeEnabled()

    /**
     * 移動(dòng)過(guò)程中繪制Item
     *
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX
     *          X軸移動(dòng)的距離
     * @param dY
     *          Y軸移動(dòng)的距離
     * @param actionState
     *          當(dāng)前Item的狀態(tài)
     * @param isCurrentlyActive
     *          如果當(dāng)前被用戶(hù)操作為true秉犹,反之為false
     */
onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)

需要注意的是蛉谜,如果我們想實(shí)現(xiàn)拖動(dòng)或者滑動(dòng)必須將上面是否支持拖動(dòng)或者滑動(dòng)的方法返回true,否則onMove或者onSwiped方法不會(huì)執(zhí)行崇堵。

功能實(shí)現(xiàn)

        adapter=new CustomAdapter(getActivity(),strings);
        recycleview.setAdapter(adapter);
        ItemTouchHelper.Callback callback=new RecycleItemTouchHelper(adapter);
        ItemTouchHelper itemTouchHelper=new ItemTouchHelper(callback);
        itemTouchHelper.attachToRecyclerView(recycleview);

對(duì)于ItemTouchHelper 構(gòu)造方法接收一個(gè)ItemTouchHelper.Callback參數(shù)型诚,而這個(gè)Callback就是我們?cè)谠谏鲜鲋v到的工具類(lèi),初始化ItemTouchHelper 后通過(guò)其attachToRecyclerView(@Nullable RecyclerView recyclerView)方法將我們實(shí)現(xiàn)的ItemTouchHelper.Callback和RecyclerView關(guān)聯(lián)鸳劳,最終達(dá)到我們的效果狰贯,代碼看起來(lái)是不是很簡(jiǎn)單,接下來(lái)我們看下我們自定義的Callback。

package com.example.xh.adapter;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.View;

import com.example.xh.R;
import com.example.xh.utils.MyApplication;

/**
 * Created by xiehui on 2017/2/23.
 */
public class RecycleItemTouchHelper extends ItemTouchHelper.Callback{
    private static final String TAG ="RecycleItemTouchHelper" ;
    private final ItemTouchHelperCallback helperCallback;

    public RecycleItemTouchHelper(ItemTouchHelperCallback helperCallback) {
        this.helperCallback = helperCallback;
    }

    /**
     * 設(shè)置滑動(dòng)類(lèi)型標(biāo)記
     *
     * @param recyclerView
     * @param viewHolder
     * @return
     *          返回一個(gè)整數(shù)類(lèi)型的標(biāo)識(shí)涵紊,用于判斷Item那種移動(dòng)行為是允許的
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        Log.e(TAG, "getMovementFlags: " );
        //START  右向左 END左向右 LEFT  向左 RIGHT向右  UP向上
        //如果某個(gè)值傳0傍妒,表示不觸發(fā)該操作,次數(shù)設(shè)置支持上下拖拽摸柄,支持向右滑動(dòng)
        return makeMovementFlags(ItemTouchHelper.UP|ItemTouchHelper.DOWN,ItemTouchHelper.END );
    }
    /**
     * Item是否支持長(zhǎng)按拖動(dòng)
     *
     * @return
     *          true  支持長(zhǎng)按操作
     *          false 不支持長(zhǎng)按操作
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return super.isLongPressDragEnabled();
    }
    /**
     * Item是否支持滑動(dòng)
     *
     * @return
     *          true  支持滑動(dòng)操作
     *          false 不支持滑動(dòng)操作
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return super.isItemViewSwipeEnabled();
    }
    /**
     * 拖拽切換Item的回調(diào)
     *
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     *          如果Item切換了位置颤练,返回true;反之塘幅,返回false
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        Log.e(TAG, "onMove: " );
        helperCallback.onMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
        return true;
    }
    /**
     * 滑動(dòng)Item
     *
     * @param viewHolder
     * @param direction
     *           Item滑動(dòng)的方向
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        Log.e(TAG, "onSwiped: ");
        helperCallback.onItemDelete(viewHolder.getAdapterPosition());
    }
    /**
     * Item被選中時(shí)候回調(diào)
     *
     * @param viewHolder
     * @param actionState
     *          當(dāng)前Item的狀態(tài)
     *          ItemTouchHelper.ACTION_STATE_IDLE   閑置狀態(tài)
     *          ItemTouchHelper.ACTION_STATE_SWIPE  滑動(dòng)中狀態(tài)
     *          ItemTouchHelper#ACTION_STATE_DRAG   拖拽中狀態(tài)
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
    }
    public interface ItemTouchHelperCallback{
        void onItemDelete(int positon);
        void onMove(int fromPosition,int toPosition);
    }
}

在默認(rèn)情況下是支持拖動(dòng)和滑動(dòng)的昔案,也就是isLongPressDragEnabled()和isItemViewSwipeEnabled()是返回true的。在該類(lèi)中我們創(chuàng)建了一個(gè)接口ItemTouchHelperCallback并創(chuàng)建兩個(gè)抽象方法电媳,分別表示拖拽和滑動(dòng)踏揣。在onMove方法中回調(diào)創(chuàng)建我們創(chuàng)建的接口方法接口onMove(int fromPosition,int toPosition),并將拖拽和 Item 的posion和目標(biāo)posion傳入匾乓,posion通過(guò)ViewHolder的getAdapterPosition()獲得捞稿,然后在滑動(dòng)回調(diào)方法onSwiped中回調(diào)onItemDelete(int positon)。到這里我們自定義的ItemTouchHelper.Callback創(chuàng)建完成拼缝。

上面完成后我們只需要在我們自定義的Adapter中實(shí)現(xiàn)RecycleItemTouchHelper.ItemTouchHelperCallback接口娱局,然后在回調(diào)方法中更新界面,如下Apdater中回調(diào)方法實(shí)現(xiàn)咧七。

    @Override
    public void onItemDelete(int positon) {
        list.remove(positon);
        notifyItemRemoved(positon);
    }

    @Override
    public void onMove(int fromPosition, int toPosition) {
        Collections.swap(list,fromPosition,toPosition);//交換數(shù)據(jù)
        notifyItemMoved(fromPosition,toPosition);
    }

我們?cè)趏nItemDelete方法中刪除對(duì)應(yīng)posion的數(shù)據(jù)衰齐,在onMove方法中通過(guò)Collections.swap方法交換對(duì)應(yīng)項(xiàng)數(shù)據(jù),然后分別調(diào)用notifyItemRemoved和notifyItemMoved通過(guò)適配器更新UI.
好了到這里功能已經(jīng)實(shí)現(xiàn)了继阻,是不是發(fā)現(xiàn)代碼很少耻涛,當(dāng)然啰嗦的比較多而已。

功能升級(jí)

通過(guò)上面簡(jiǎn)單代碼的實(shí)現(xiàn)瘟檩,已經(jīng)可以滑動(dòng)刪除和拖拽了抹缕,當(dāng)然不滿(mǎn)足的你可能發(fā)現(xiàn)滑動(dòng)刪除的時(shí)候沒(méi)有動(dòng)畫(huà)沒(méi)有背景,但是我想更改下背景并且在滑動(dòng)的過(guò)程會(huì)出現(xiàn)一個(gè)刪除的圖標(biāo)墨辛,給用戶(hù)反饋卓研,讓其明白該操作是刪除數(shù)據(jù)的。當(dāng)然你還會(huì)想再刪除的過(guò)程中增加一個(gè)動(dòng)畫(huà)睹簇。其實(shí)實(shí)現(xiàn)這個(gè)效果也并不是很麻煩奏赘,接下來(lái)新的方法實(shí)現(xiàn)登場(chǎng)。哦太惠,不對(duì)志珍,前面提到過(guò)的。

 onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)

該方法就是移動(dòng)過(guò)程中繪制Item的回調(diào)垛叨。我們?cè)赼ctionState==ItemTouchHelper.ACTION_STATE_SWIPE時(shí)伦糯,即為滑動(dòng)的時(shí)候繪制背景和刪除圖片柜某。
初始化

  /**
     * 移動(dòng)過(guò)程中繪制Item
     *
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX
     *          X軸移動(dòng)的距離
     * @param dY
     *          Y軸移動(dòng)的距離
     * @param actionState
     *          當(dāng)前Item的狀態(tài)
     * @param isCurrentlyActive
     *          如果當(dāng)前被用戶(hù)操作為true,反之為false
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        //滑動(dòng)時(shí)自己實(shí)現(xiàn)背景及圖片
        if (actionState==ItemTouchHelper.ACTION_STATE_SWIPE){

            //dX大于0時(shí)向右滑動(dòng)敛纲,小于0向左滑動(dòng)
            View itemView=viewHolder.itemView;//獲取滑動(dòng)的view
            Resources resources= MyApplication.getAppContext().getResources();
            Bitmap bitmap= BitmapFactory.decodeResource(resources, R.drawable.delete);//獲取刪除指示的背景圖片
            int padding =10;//圖片繪制的padding
            int maxDrawWidth=2*padding+bitmap.getWidth();//最大的繪制寬度
            Paint paint=new Paint();
            paint.setColor(resources.getColor(R.color.btninvalid));
            int x=Math.round(Math.abs(dX));
            int drawWidth=Math.min(x,maxDrawWidth);//實(shí)際的繪制寬度喂击,取實(shí)時(shí)滑動(dòng)距離x和最大繪制距離maxDrawWidth最小值
            int itemTop=itemView.getBottom()-itemView.getHeight();//繪制的top位置
            //向右滑動(dòng)
            if(dX>0){
              //根據(jù)滑動(dòng)實(shí)時(shí)繪制一個(gè)背景
                c.drawRect(itemView.getLeft(),itemTop,drawWidth,itemView.getBottom(),paint);
                //在背景上面繪制圖片
                if (x>padding){//滑動(dòng)距離大于padding時(shí)開(kāi)始繪制圖片
                    //指定圖片繪制的位置
                    Rect rect=new Rect();//畫(huà)圖的位置
                    rect.left=itemView.getLeft()+padding;
                    rect.top=itemTop+(itemView.getBottom()-itemTop-bitmap.getHeight())/2;//圖片居中
                    int maxRight=rect.left+bitmap.getWidth();
                    rect.right=Math.min(x,maxRight);
                    rect.bottom=rect.top+bitmap.getHeight();
                    //指定圖片的繪制區(qū)域
                    Rect rect1=null;
                    if (x<maxRight){
                        rect1=new Rect();//不能再外面初始化,否則dx大于畫(huà)圖區(qū)域時(shí)淤翔,刪除圖片不顯示
                        rect1.left=0;
                        rect1.top = 0;
                        rect1.bottom=bitmap.getHeight();
                        rect1.right=x-padding;
                    }
                    c.drawBitmap(bitmap,rect1,rect,paint);
                }
                //繪制時(shí)需調(diào)用平移動(dòng)畫(huà)翰绊,否則滑動(dòng)看不到反饋
                itemView.setTranslationX(dX);
            }else {
                //如果在getMovementFlags指定了向左滑動(dòng)(ItemTouchHelper。START)時(shí)則繪制工作可參考向右的滑動(dòng)繪制旁壮,也可直接使用下面語(yǔ)句交友系統(tǒng)自己處理
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        }else {
            //拖動(dòng)時(shí)有系統(tǒng)自己完成
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }
    }

經(jīng)過(guò)上面的處理监嗜,發(fā)現(xiàn)此時(shí)滑動(dòng)可以看到我們定制的藍(lán)低的刪除背景了,此時(shí)可能還有疑問(wèn)抡谐,這樣刪除是不是很生硬裁奇,那就再給它加一個(gè)透明度的動(dòng)畫(huà),如下麦撵。

                float alpha = 1.0f - Math.abs(dX) / (float) itemView.getWidth();
                itemView.setAlpha(alpha);

好了刽肠,到這里RecyclerView實(shí)現(xiàn)滑動(dòng)刪除和拖拽功能的已經(jīng)介紹完畢了。有問(wèn)題歡迎留言指出免胃,Have a wonderful day .

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末音五,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子羔沙,更是在濱河造成了極大的恐慌躺涝,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扼雏,死亡現(xiàn)場(chǎng)離奇詭異坚嗜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)呢蛤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)棍郎,“玉大人其障,你說(shuō)我怎么就攤上這事⊥康瑁” “怎么了励翼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)辜荠。 經(jīng)常有香客問(wèn)我汽抚,道長(zhǎng),這世上最難降的妖魔是什么伯病? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任造烁,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惭蟋。我一直安慰自己苗桂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布告组。 她就那樣靜靜地躺著煤伟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪木缝。 梳的紋絲不亂的頭發(fā)上便锨,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音我碟,去河邊找鬼放案。 笑死,一個(gè)胖子當(dāng)著我的面吹牛怎囚,可吹牛的內(nèi)容都是我干的卿叽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恳守,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼考婴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起催烘,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沥阱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后伊群,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體考杉,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年舰始,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崇棠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丸卷,死狀恐怖枕稀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谜嫉,我是刑警寧澤萎坷,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站沐兰,受9級(jí)特大地震影響哆档,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜住闯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一瓜浸、第九天 我趴在偏房一處隱蔽的房頂上張望澳淑。 院中可真熱鬧,春花似錦斟叼、人聲如沸偶惠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忽孽。三九已至,卻和暖如春谢床,著一層夾襖步出監(jiān)牢的瞬間兄一,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工识腿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留出革,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓渡讼,卻偏偏與公主長(zhǎng)得像骂束,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子成箫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容