RecyclerView 知識梳理(5) - ItemTouchHelper

一、概述

ItemTouchHelperRecyclerView的整個體系中憔四,負(fù)責(zé)監(jiān)聽Item的手勢操作缎罢,我們通過給它設(shè)置一個繼承于ItemTouchHelper.Callback的子類,在其中處理ItemUI變化窘疮,就可以完成側(cè)滑刪除骑素、拖動排序等操作炫乓,下面,我們分以下幾部介紹:

  • API解析
  • 實戰(zhàn)
  • 采用默認(rèn)動畫
  • 自定義側(cè)滑刪除動畫

二献丑、API分析

對于Item的手勢操作分為兩種:側(cè)滑和拖動末捣,如果需要支持這兩種,那么需要給ItemTouchHelper傳入一個ItemTouchHelper.Callback的子類阳距,并把ItemTouchHelperRecyclerView關(guān)聯(lián)起來塔粒,下面结借,我們先來介紹一下ItemTouchHelper.Callback個回調(diào)方法的含義:

控制相關(guān)

  • public boolean isLongPressDragEnabled()
    是否可以通過長按來觸發(fā)拖動操作筐摘,默認(rèn)返回true,如果返回false船老,那么可以通過startDrag(ViewHolder)方法來觸發(fā)某個特定Item的拖動的機制咖熟。
  • public boolean isItemViewSwipeEnabled()
    是否可以對每個Item進行側(cè)滑。
  • public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
    返回對于某個ViewHolder可以移動的方向柳畔,可選的值有UP/DOWN/LEFT/RIGHT/START/END馍管。對于縱向排列的線性布局而言,如果要支持上下拖動排序薪韩,那么就要標(biāo)志位中就要包含UP&DOWN确沸,而如果需要支持左滑刪除捌锭,那么標(biāo)志位中就要包含LEFT

結(jié)果相關(guān)

  • public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target)
    當(dāng)某個被拖動的Item被從舊位置拖動到了新位置后回調(diào)罗捎,如果返回true观谦,那么ItemTouchHelper就認(rèn)為viewHolder已經(jīng)被移動到了targetAdapter中的位置。
  • public abstract void onSwiped(ViewHolder viewHolder, int direction)
    當(dāng)某個Item被滑動到消失時回調(diào)桨菜,direction表示滑動的方向豁状。

狀態(tài)相關(guān)

  • public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
    當(dāng)Item的狀態(tài)發(fā)生改變時,回調(diào)該方法倒得,actionState的取值有ACTION_STATE_IDLE/ACTION_STATE_SWIPE/ACTION_STATE_DRAG泻红。

  • public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
    標(biāo)志著用戶對于某個Item的操作并且Item的動畫結(jié)束,此時我們應(yīng)該恢復(fù)它的狀態(tài)霞掺,以保證它被重新使用的時候能正確地展現(xiàn)谊路。

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

  • Canvas:繪制RecyclerViewCanvas

  • dx, dy:偏移。

  • actionState:拖拽還是側(cè)滑菩彬,對應(yīng)ACTION_STATE_DRAGACTION_STATE_SWIPE凶异。

  • isCurrentlyActivetrue表示這個Item正在被用戶所控制,false則表示它僅僅是在回到原本狀態(tài)的動畫過程當(dāng)中挤巡。

  • public void onChildDrawOver(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
    和上面類似剩彬,只不過它是繪制在Item之上。

三矿卑、實戰(zhàn)

3.1 使用系統(tǒng)默認(rèn)效果

如果我們希望使用系統(tǒng)默認(rèn)的效果喉恋,那么只需要做以下幾步:

  • 繼承于ItemTouchHelper.Callback編寫自己的回調(diào)類,并在拖動和側(cè)滑操作完成之后更新數(shù)據(jù):
public class SimpleItemTouchHelper extends ItemTouchHelper.Callback {

    private ItemTouchAdapter mAdapter;

    public SimpleItemTouchHelper(ItemTouchAdapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        Log.d("SimpleItemTouchHelper", "onSwiped, onMove, source=" + viewHolder.getAdapterPosition() + ",target=" + target.getAdapterPosition());
        mAdapter.onItemDragged(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        Log.d("SimpleItemTouchHelper", "onSwiped, direction=" + direction);
        mAdapter.onItemSwiped(viewHolder.getAdapterPosition());
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
    }
}
  • 編寫數(shù)據(jù)操作的代碼:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> implements ItemTouchAdapter {

    //......

    @Override
    public void onItemDragged(int from, int to) {
        Collections.swap(mTitles, from, to);
        notifyItemMoved(from, to);
    }

    @Override
    public void onItemSwiped(int position) {
        mTitles.remove(position);
        notifyItemRemoved(position);
    }
}
  • ItemTouchHelper.CallbackRecyclerView關(guān)聯(lián)起來母廷,看注釋中的1,2,3步:
    private void init() {
        List<String> titles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            titles.add(String.valueOf(i));
        }
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        recyclerView.setLayoutManager(layoutManager);
        NormalAdapter adapter = new NormalAdapter(titles);
        //1.自定義的ItemTouchHeloer.Callback
        SimpleItemTouchHelper simpleItemTouchHelper = new 
SimpleItemTouchHelper(adapter);
        //2.利用這個Callback構(gòu)造ItemTouchHelper
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchHelper);
        //3.把ItemTouchHelper和RecyclerView關(guān)聯(lián)起來.
        itemTouchHelper.attachToRecyclerView(recyclerView);
        recyclerView.setAdapter(adapter);
    }

下面就是最終的效果:


3.2 自定義側(cè)滑刪除動畫

當(dāng)我們需要自定側(cè)滑刪除動畫時轻黑,那么需要重寫onChildDraw或者onChildDrawOver方法,在其中監(jiān)聽滑動距離的變化琴昆,并根據(jù)它來實時改變viewHolder中的UI氓鄙,首先看效果:

  • 首先,我們需要重寫Item的布局业舍,它包含兩層抖拦,頂層是普通狀態(tài)的標(biāo)題文案,而底層則是藍(lán)色底的刪除提示:
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="66dp">
    <!-- 刪除提示 -->
    <LinearLayout
        android:id="@+id/ll_delete"
        android:orientation="vertical"
        android:gravity="center"
        android:layout_gravity="end"
        android:background="@android:color/holo_blue_dark"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">
        <ImageView
            android:src="@android:drawable/ic_input_delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:text="delete"
            android:textColor="@android:color/white"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    <!-- 普通文案 -->
    <TextView
        android:id="@+id/tv_title"
        android:gravity="center"
        android:background="@android:color/white"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

接著舷暮,我們需要重寫ItemTouchHelper.Callback

public class AdvancedItemTouchHelper extends ItemTouchHelper.Callback {

    private ItemTouchAdapter mAdapter;

    public AdvancedItemTouchHelper(ItemTouchAdapter itemTouchAdapter) {
        mAdapter = itemTouchAdapter;
    }

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

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return makeMovementFlags(0, ItemTouchHelper.LEFT);
    }

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

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        ((NormalAdapter.NormalViewHolder) viewHolder).mTv.setTranslationX(0);
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        NormalAdapter.NormalViewHolder mViewHolder = (NormalAdapter.NormalViewHolder) viewHolder;
        int deleteWidth = mViewHolder.mDeleteLayout.getWidth();
        float fraction = deleteWidth / (float) mViewHolder.itemView.getWidth();
        mViewHolder.mTv.setTranslationX(dX * fraction);
    }
}

這里面有幾點需要注意:

  • 為了讓Item支持左滑刪除态罪,我們需要在getMovementFlags中返回ItemTouchHelper.LEFT標(biāo)志位。
  • onChildDraw當(dāng)中下面,通過傳入的dX動態(tài)改變了普通文案的translationX复颈,使得底層的刪除提示能夠漏出。
  • 在側(cè)滑操作完成之后沥割,通過Adapter來刪除數(shù)據(jù)耗啦。
  • clearView中凿菩,需要把mTv重置為初始的狀態(tài)。

最后帜讲,我們按照前面的方法蓄髓,把它和RecyclerView關(guān)聯(lián)起來:

    private void init() {
        List<String> titles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            titles.add("Item " + String.valueOf(i));
        }
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        recyclerView.setLayoutManager(layoutManager);
        NormalAdapter adapter = new NormalAdapter(titles);
        AdvancedItemTouchHelper advancedItemTouchHelper = new AdvancedItemTouchHelper(adapter);
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(advancedItemTouchHelper);
        itemTouchHelper.attachToRecyclerView(recyclerView);
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(adapter);
    }

四、小結(jié)

自定義RecyclerView的手勢動畫舒帮,關(guān)鍵是要理解ItemTouchHelper.Callback中各回調(diào)函數(shù)的含義会喝,再通過回調(diào)函數(shù)中傳入的數(shù)值來動態(tài)改變viewHolder中保存的itemView以及其子View的展現(xiàn)形式,就可以做出各種絢麗的效果玩郊。

五肢执、參考文獻(xiàn)

RecyclerView 進階:使用 ItemTouchHelper 實現(xiàn)拖拽和側(cè)滑刪除


更多文章,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末译红,一起剝皮案震驚了整個濱河市预茄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侦厚,老刑警劉巖耻陕,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刨沦,居然都是意外死亡诗宣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門想诅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來召庞,“玉大人,你說我怎么就攤上這事来破±鹤疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵徘禁,是天一觀的道長诅诱。 經(jīng)常有香客問我,道長送朱,這世上最難降的妖魔是什么娘荡? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮骤菠,結(jié)果婚禮上它改,老公的妹妹穿的比我還像新娘。我一直安慰自己商乎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布祭阀。 她就那樣靜靜地躺著鹉戚,像睡著了一般鲜戒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抹凳,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天遏餐,我揣著相機與錄音,去河邊找鬼赢底。 笑死失都,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幸冻。 我是一名探鬼主播粹庞,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洽损!你這毒婦竟也來了庞溜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤碑定,失蹤者是張志新(化名)和其女友劉穎流码,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體延刘,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡漫试,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碘赖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片商虐。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖崖疤,靈堂內(nèi)的尸體忽然破棺而出秘车,到底是詐尸還是另有隱情,我是刑警寧澤劫哼,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布叮趴,位于F島的核電站,受9級特大地震影響权烧,放射性物質(zhì)發(fā)生泄漏眯亦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一般码、第九天 我趴在偏房一處隱蔽的房頂上張望妻率。 院中可真熱鬧,春花似錦板祝、人聲如沸宫静。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孤里。三九已至伏伯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捌袜,已是汗流浹背说搅。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虏等,地道東北人弄唧。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像霍衫,于是被迫代替她去往敵國和親候引。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

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