咱們平時在QQ過程當中,有一個效果是咱們不可忽略的,那就是消息記錄的側滑刪除功能,我相信這個功能不少人都用過吧。不得不說,QQ的交互確實作得很是優秀。今天,咱們模仿一下QQ的側滑效果(這自己是樓主畢設裏面的一個小效果,我只是將它分離出來),首先來看看Demo的效果: git
ItemTouchHelper
,若是還有同窗沒有學過,能夠參考個人文章:
在正式講解如上的效果是怎麼實現以前,咱們來看看上面效果都有那幾個問題須要考慮的:github
- 怎麼實現左右側滑?
ItemTouchHelper
?ItemTouchHelper
怎麼實現側滑以後不刪除,而是停留在某種狀態上。- 從上面的效果圖中,咱們能夠明確的感覺到左右滑動均可以超出
RecyclerView
的邊界,只是在手指鬆開以後,ItemView
會回到正確的位置而已。像這種overscroll
效果應該怎麼實現?- 當停留在刪除狀態時,此時
ItemView
還能夠從左右滑動(QQ好像只能向右滑動😂)。這種效果又應該怎麼實現呢?
針對上面的三個問題,本文會詳細的分析是怎麼實現的。 #2. 再來了解ItemTouchHelper.Callback 咱們知道,ItemTouchHelper
的核心在於ItemTouchHelper.Callback
接口中,因此在正式分析實現以前,咱們先來了解temTouchHelper.Callback
的幾個方法。bash
方法名 | 做用 |
---|---|
getSwipeThreshold | 當 側滑滑動的距離 / RecyclerView 的寬大於該方法返回值,那麼就會觸發側滑刪除的操做。具體是:此時ItemView 會作位移動畫,當ItemView 不可見時,會觸發ItemTouchHelper 的onSwiped 方法,進而咱們在onSwiped 方法裏面對Adapter進行remove操做。 |
getSwipeEscapeVelocity | 當側滑的速度大於該方法的返回值,也會觸發側滑刪除的操做。 |
onChildDraw | 此方法是本文的核心方法。該方法在ItemView 進行滑動時會回調,這裏的滑動包括:1.手指滑動;2.ItemView 的位移動畫。能夠根據isCurrentlyActive 參數來判斷是手指滑動仍是動畫滑動。 |
clearView | 此方法是本文的核心方法。該方法在ItemView 滑動完成以後會回調,因此想要實現側滑ItemView 停在某種狀態,此方法是核心之點。 |
通過對上面幾個方法的理解,如今,我來簡單描述一下效果的實現方法: 首先咱們將滑動距離的閾值和滑動速度的閾值設置到最大,保證不會觸發本來的側滑刪除操做。 當手指滑動時,咱們就讓它開心的滑動,不去怎麼作任何的操做;當手指鬆開時,由於速度閾值和距離閾值都是最大,因此不可能超過它,那麼ItemView
會作動畫回到最初的狀態,可是咱們不能按照原來的規則來位移ItemView
,因此咱們得判斷手指鬆開那時ItemView
的滑動距離是否大於咱們設置的閾值(是否顯示刪除按鈕的閾值),若是大於的話,那麼就作動畫到給定的位置就行,若是小於的話,就回到原始位置。ide
如今咱們正式來看一下代碼,首先看一下getSwipeThreshold
方法和getSwipeEscapeVelocity
方法:動畫
@Override
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return Integer.MAX_VALUE;
}
@Override
public float getSwipeEscapeVelocity(float defaultValue) {
return Integer.MAX_VALUE;
}
複製代碼
我這裏均設置爲Integer.MAX_VALUE
。其實getSwipeThreshold
不用設置那麼大,設置爲1.1應該就好了,畢竟是百分比。 如今咱們來看看onChildDraw
方法的實現:ui
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
// 首次滑動時,記錄下ItemView當前滑動的距離
if (dX == 0) {
mCurrentScrollX = viewHolder.itemView.getScrollX();
mFirstInactive = true;
}
if (isCurrentlyActive) { // 手指滑動
// 基於當前的距離滑動
viewHolder.itemView.scrollTo(mCurrentScrollX + (int) -dX, 0);
} else { // 動畫滑動
if (mFirstInactive) {
mFirstInactive = false;
mCurrentScrollXWhenInactive = viewHolder.itemView.getScrollX();
mInitXWhenInactive = dX;
}
if (viewHolder.itemView.getScrollX() >= mDefaultScrollX) {
// 當手指鬆開時,ItemView的滑動距離大於給定閾值,那麼最終就停留在閾值,顯示刪除按鈕。
viewHolder.itemView.scrollTo(Math.max(mCurrentScrollX + (int) -dX, mDefaultScrollX), 0);
} else {
// 這裏只能作距離的比例縮放,由於回到最初位置必須得從當前位置開始,dx不必定與ItemView的滑動距離相等
viewHolder.itemView.scrollTo((int) (mCurrentScrollXWhenInactive * dX / mInitXWhenInactive), 0);
}
}
}
複製代碼
須要注意的是,爲了不滑動時出現跳變,因此咱們在手動時,必須基於當前的滑動距離來滑動,由於每一次手指滑動開始,dx都是從0開始的,這樣就表示每一次手指滑動ItemView
都從最初的位置開始滑動。 還須要注意一點,就是當手指中止滑動時,咱們須要判斷ItemView
最終應該處於那種狀態。這裏說的狀態無非是兩種,一種是刪除狀態,一種最初狀態。判斷了狀態以後,咱們須要將ItemView
位移到指定位置,這裏兩種方式:spa
- 本身實現動畫,來滑動到指定位置。
- 經過
onChildDraw
方法的回調來滑動到指定位置。
這裏我使用的是第二種方式,相對來講,第一種方式比較簡單,畢竟是本身實現動畫;而第二種方式須要dx的值。具體的細節你們能夠看我代碼上的註釋。code
咱們再來看clearView
方法的實現:cdn
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (viewHolder.itemView.getScrollX() > mDefaultScrollX) {
viewHolder.itemView.scrollTo(mDefaultScrollX, 0);
} else if (viewHolder.itemView.getScrollX() < 0) {
viewHolder.itemView.scrollTo(0, 0);
}
mItemTouchStatus.onSaveItemStatus(viewHolder);
}
複製代碼
clearView
方法的做用就是當ItemView
滑動完成以後,進行一些處理,這裏咱們必須保證ItemView
滑動到指定位置,由於在計算過程當中不免會有一點點偏差,因此要進行歸位處理。接口
爲了方便你們的理解,我將個人代碼上傳到github:SlideDeleteDemo