RecyclerView 梳理:點選&長按事件、分割線、拖曳排序、滑動刪除

小鴻洋發表於2018-07-19

本文作者

作者:OCNYang

連結:http://www.jianshu.com/p/70788a7a5547

本文由作者投稿釋出。

這次主要是把 RecyclerView 比較常用的基本的點,在這裡集中整理一下。從這篇文章主要梳理以下幾點:

  • 優雅的實現:item 點選事件 & item 長點選事件

  • RecyclerView 新增 divider 的標準姿勢

  • RecyclerView 實現 item 的拖曳排序和滑動刪除

  • 拖曳排序時,限制首個 item 固定的實現

先看一下最終的效果圖:

--swipe and drag--

--drag--

自從 RecyclerView 釋出以來,由於其高度的可互動性被廣泛使用。相信大家肯定對它的使用方法已經非常熟練了,今天主要是為大家總結一下較正常用法更加優雅的方式。

如果你想再回顧一下 RecyclerView 的基本使用方法,推薦鴻洋的這篇文章:

《Android RecyclerView 使用完全解析 體驗藝術般的控制元件》

http://blog.csdn.net/lmj623565791/article/details/45059587

1

使用方式

RecyclerView 的 api 雖然沒有提供 onItemClickListener 但是提供了 addOnItemTouchListener() 方法,既然可以新增觸控監聽,那麼我們完全可以獲取觸控手勢來識別點選事件,然後通過觸控座標來判斷點選的是哪一個item。

其中 OnRecyclerItemClickListener 是自定義的一個觸控監聽器,程式碼如下:

GestureDetectorCompat 中傳入了一個 ItemTouchHelperGestureListener,程式碼如下:

原理分析

上面的程式碼很簡單沒什麼複雜的地方,就是通過一個手勢探測器 GestureDetectorCompat 來探測螢幕事件,然後通過手勢監聽器 SimpleOnGestureListener 來識別手勢事件的種類,然後呼叫我們設定的對應的回撥方法。

這裡值得說的是:當獲取到了 RecyclerView 的點選事件和觸控事件資料 MotionEvent,那麼如何才能知道點選的是哪一個 item 呢?

RecyclerView已經為我們提供了這樣的方法:findChildViewUnder()。

我們可以通過這個方法獲得點選的 item ,同時我們呼叫 RecyclerView 的另一個方法 getChildViewHolder(),可以獲得該 item 的 ViewHolder,最後再回撥我們定義的虛方法 onItemClick() 就ok了,這樣我們就可以在外部實現該方法來獲得 item 的點選事件了。

2

新增 divider 的標準姿勢

當你想給條目間新增 divider 時,你可能自然而然的去嘗試這種方式:

<android.support.v7.widget.RecyclerView android:divider="#ffff0000" android:dividerHeight="10dp" android:layout_width="match_parent" android:layout_height="match_parent" />

其實 RecyclerView 是沒有這兩個屬性的,就算你寫上也不會有任何效果。

當然你還可以通過給 item 的最外層佈局設定一個 margin 值,甚至你還可以專門在 item 佈局中的適當地方新增一個高度/寬度為 1 的帶背景的 View 作為 divider,這兩種方法呢,確實有效果,但是不夠優雅,有時還可能帶來一些想不到的問題。

其實官方還是為我們提供了為 RecyclerView 新增分割線的方式的,那就是方法: mRecyclerView.addItemDecoration()。

該方法的引數為 RecyclerView.ItemDecoration,該類為抽象類,且官方目前並沒有提供預設的實現類,我們只能自己來實現。

注意:作者這裡給出了列表佈局和網格佈局分別實現分割線的實現類,由於程式碼太長,可以跳轉到原文檢視。

上面給出的兩個例項都是最簡單的一條線的分割。這裡的分割線你是可以自由的去自定義它的,具體如何實現也不是太複雜,這裡不再做詳細介紹了,推薦一篇文章:

《RecyclerView之ItemDecoration 講解及高階特性實踐》

http://www.10tiao.com/html/227/201705/2650239745/1.html

3

實現 item 的拖曳排序和滑動刪除

下面就主要為大家梳理一下拖曳排序和滑動刪除的實現,具體實現效果看文章首部效果圖,這裡就不再重複放圖了。

實現方式

主要就要使用到 ItemTouchHelper,ItemTouchHelper 一個幫助開發人員處理拖拽和滑動刪除的實現類,它能夠讓你非常容易實現側滑刪除、拖拽的功能。

(ItemTouchHelper 的使用並不僅僅侷限於 RecyclerView 的滑動刪除,你同意可以用在其他需要拖曳滑動的地方。當然,今天我們不涉及其他地方的使用)

實現的程式碼並關聯到 RecyclerView 非常簡單,程式碼如下:

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

程式碼很簡單,沒什麼好說的。

需要我們關注的是建立 ItemTouchHelper 時傳入的引數 ItemTouchHelper.Callback() 。ItemTouchHelper 會在拖拽的時候回撥 Callback 中相應的方法,我們只需在 Callback 中實現自己的邏輯。

自定義一個類繼承實現 ItemTouchHelper.Callback 介面,需要實現以下方法:

getMovementFlags() 用於設定是否處理拖拽事件和滑動事件,以及拖拽和滑動操作的方向,有以下兩種情況:

  • 如果是列表型別的 RecyclerView,拖拽只有 UP、DOWN 兩個方向

  • 如果是網格型別的則有 UP、DOWN、LEFT、RIGHT 四個方向

該方法需要編寫的程式碼如下:

dragFlags 是拖拽標誌,

swipeFlags 是滑動標誌,

swipeFlags 都設定為0,暫時不考慮滑動相關操作。

如果設定了相關的 dragFlags,那麼當長按 item 的時候就會進入拖拽並在拖拽過程中不斷回撥 onMove() 方法,我們就在這個方法裡獲取當前拖拽的 item 和已經被拖拽到所處位置的 item 的ViewHolder,有了這2個 ViewHolder,我們就可以交換他們的資料集並呼叫 Adapter 的notifyItemMoved 方法來重新整理 item。

只要重寫完上面這兩個方法,RecyclerView 就能實現拖曳的效果了。是不是很簡單?

但是雖然拖曳是沒什麼問題了,但是並不能達到下圖的效果,因為你正在拖曳的 item 並沒有陰影效果。

那怎麼才能實現被拖曳的 item 有背景顏色加深起到強調的視覺效果呢?這是需要重寫下面兩個方法:

這樣就能完全達到上面圖片的效果了。

滑動刪除

如何實現滑動刪除呢?我們只需要實現第三個方法 onSwipe() 就行了。

程式碼如下:

同時也不要忘了修改一下 getMovementFlags() 方法,以便能夠相應滑動事件:

那目前你就能完美的實現拖曳排序和滑動刪除了。

拖曳排序,首個固定

有時我們希望首個 item 不能被拖曳排序。比如我們在新聞 App 中常見當我們進行新聞分類時,“熱門”新聞這個分類總是第一個且不能被拖曳修改,類似下面的效果:

那麼怎麼才能達到上面的效果呢?在上面我們的 Callback 類中有一個方法:

public boolean isLongPressDragEnabled() { return true;}

這個方法是為了告訴 ItemTouchHelper 是否需要 RecyclerView 支援長按拖拽,預設返回是 ture,理所當然我們要支援,所以我們沒有重寫,因為預設true。

但是這樣做是預設全部的item都可以拖拽,怎麼實現部分item拖拽呢,在 isLongPressDragEnabled 方法的原始碼中有提示說,如果想自定義拖曳 view,那麼就使用 startDrag(ViewHolder) 方法。

第一步:那麼我們就先重寫 isLongPressDragEnabled() 方法,返回 false 讓它控制所有的 item 都不能拖曳。

public boolean isLongPressDragEnabled() { return false;}

第二步:我們給 RecyclerView 設定 item 的長按監聽事件,然後判斷這個 item 是不是第一個(或者最後一個,如果你不想讓最後一個被拖曳的話),如果不是我們就手動呼叫 startDrag(ViewHolder) 讓 item 開始被拖曳。

結合上面我們提供的給 item 設定點選和長按事件的方法,我們可以這樣:

第三步:如果你以為上面兩步你就達到首個 item 固定不被拖曳的話,恭喜你,答對了!首個 item 確實固定不能被拖曳了,可是看看下圖,就會令你大跌眼睛:

雖然我們通過上面兩步控制了首個 item 不能被長按拖曳,但是我們並沒有處理,別的 item 被拖曳到首個 item 的情況。那麼如何才能讓首個 item 不被擠掉呢,這個也很簡單,只需要在 Callback 的 onMove() 方法中處理首個 item 被當著目標 item 的情況就行了。

好了,到這裡就大功告成了。

本文原始碼地址:

https://github.com/OCNYang/RecyclerViewEvent

參考文章:

http://chuansong.me/n/400690551872

http://chuansong.me/n/400690851058

http://www.10tiao.com/html/227/201705/2650239745/1.html

相關文章