實現(xiàn)可自定義的Android滑動刪除

序言

最近項目中需要用到滑動刪除,然后去網(wǎng)上搜了一下,發(fā)現(xiàn)現(xiàn)有網(wǎng)上的各種解決辦法各式各樣,但是還是找不到一個能將所有細(xì)節(jié)和邏輯處理好的,至于滑動刪除部分,我覺得處理的相對比較好的是 QQ(包括處理各種邏輯和細(xì)節(jié));最終,苦尋無果,于是決定自己動手,豐衣足食

  • 這篇文章將從現(xiàn)有 Android 滑動刪除的痛點,到搭建好一個基本的框架,到最終提供一份完整的 Demo為止,爭取為讀者提供最大的可定制化

正文

一. 滑動刪除的痛點

(1). 現(xiàn)有資料中的不足

  1. 筆者參閱了網(wǎng)上的一些博客,發(fā)現(xiàn),這些博客中大多能夠基本實現(xiàn)滑動刪除,但是存在的問題是,對于面向用戶實際使用而言,卻是遠(yuǎn)遠(yuǎn)不夠的
  2. 大多數(shù)博客實現(xiàn)的只是當(dāng)手指 DOWN 的時候,通過判斷左右滑動和上下滑動的距離之比來判斷 Item 是否應(yīng)該滑動;但是有一個問題就是,用戶 DOWN 的時候獲得焦點的 Item ,但是 MOVE 的時候手指離開了該 Item 的時候應(yīng)該如何處理呢? 按照正常的用戶邏輯,這時仍然應(yīng)該是該 Item 處理滑動事件
  3. 最重要和最難的部分當(dāng)然也是滑動沖突了,即不管使用 RecyclerView 還是使用 ListView 實現(xiàn),其都存在處理上下滑動和左右滑動的沖突問題,很明顯的是我們不能一味地攔截所有事件,因為對于上下滑動事件還需要交給 RecyclerView/ListView 來實現(xiàn)正常的上下滑動;滑動沖突部分如果處理不好的話會出現(xiàn)很明顯的卡頓現(xiàn)象,同時也會出現(xiàn)不符合用戶心理預(yù)期的響應(yīng),而這些都是用戶不友好的
  4. 另外,現(xiàn)有的資料都是在自己的代碼實現(xiàn)上講解的,對于實現(xiàn)正真的定制化還是很有難度的,當(dāng)我們想要實現(xiàn)自己想要的功能時,我們還需要去看懂一些不相關(guān)的處理邏輯

(2). 需要處理的細(xì)節(jié)

  1. 我一直覺得 QQ 在處理滑動刪除上做的是相對比較好的,特別是從各種細(xì)節(jié)處理上,它基本上都能給出符合用戶心理預(yù)期的響應(yīng),這里也是以 QQ 為例來介紹幾種需要注意和處理的細(xì)節(jié);當(dāng)然,需要注意的地方很多,一一例舉不太現(xiàn)實,具體的還是需要自己動手啦
  2. 側(cè)滑過程中,DOWN 時得到焦點的 Item 在 MOVE 過程中失去了焦點應(yīng)該怎么處理?(即對應(yīng)上面的 現(xiàn)有資料中的不足中的第2項);如下圖所示,手指 DOWN 的時候得到焦點的是 Item 7, 但是之后手指在 MOVE 過程中,Item 7 失去了焦點;正如上面所說,此時還是應(yīng)該交由該 Item 7 處理滑動事件(如果在 DOWN 的時候已經(jīng)判為側(cè)滑的話)
失去焦點.png
  1. 如果當(dāng)前有 Item 正在側(cè)滑,那么 RecyclerView 就不能再同時上下滑動
  2. 如果當(dāng)前有 Item 處于打開狀態(tài),那么在下一次 DOWN 的時候應(yīng)該先將其關(guān)閉,同時在 UP 之前,MOVE 事件都應(yīng)該是無效的(對于這種情況,也可以按照自己的邏輯處理,如: 如果當(dāng)前有 Item 處于打開狀態(tài),那么在下一次 DOWN 的時候應(yīng)該先將其關(guān)閉,但是在關(guān)閉之后,在 UP 之前出現(xiàn)的 MOVE 事件也應(yīng)該響應(yīng))
  3. 在一次 DOWN->MOVE...MOVE->UP 的完整過程中,一旦初始判斷決定了應(yīng)該是上下滑動或者 Item 的左右滑動之后,在 MOVE 過程中就不能改變,直至下一次新的判斷過程為止(這種情況容易出現(xiàn)在用戶在一次過程中反復(fù)的上下滑動時突然來一次左右滑動(或者反復(fù)的左右滑動過程中,突然來一次上下滑動))

二. 一個框架

(1). 使用 RecyclerView 搭建框架

1. 預(yù)備知識

  • RecyclerView 對外提供的接口已經(jīng)比較完善,所以不需要再去繼承 RecyclerView 來監(jiān)聽其 MotionEvent 事件
  • 可以通過 RecyclerView 的 addOnItemTouchListener() 方法來實現(xiàn)對所有 MotionEvent 的攔截,其需要傳入一個 RecyclerView.OnItemTouchListener 對象,這是一個 interface ,需要我們自己來實現(xiàn)邏輯,這里筆者寫了一個大致的 Demo 先來看看其各個方法之間的聯(lián)系
  • recyclerView.addOnItemTouchListener(new   RecyclerView.OnItemTouchListener() {
    
              @Override
              public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                  switch (e.getAction()) {
                      case MotionEvent.ACTION_DOWN: {
                          Log.d("@HusterYP", String.valueOf("onInterceptTouchEvent  DOWN"));
                          break;
                      }
                      case MotionEvent.ACTION_MOVE: {
                          Log.d("@HusterYP", String.valueOf("onInterceptTouchEvent  MOVE"));
                          break;
                      }
                      case MotionEvent.ACTION_UP: {
                          Log.d("@HusterYP", String.valueOf("onInterceptTouchEvent  UP"));
                          break;
                      }
                  }
                  return true;
              }
    
              @Override
              public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                  switch (e.getAction()) {
                      case MotionEvent.ACTION_MOVE: {
                          Log.d("@HusterYP", String.valueOf("onTouchEvent MOVE"));
    
                          break;
                      }
                      case MotionEvent.ACTION_UP: {
                          Log.d("@HusterYP", String.valueOf("onTouchEvent UP"));
                          break;
                      }
                  }
              }
    
              @Override
              public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
              }
          });
    
  • 關(guān)于該 Demo 的代碼可至筆者Github上下載執(zhí)行測試;這里筆者就直接給出在 onInterceptTouchEvent 方法中返回不同值時的結(jié)論了:
  1. 如果在最后返回 false,那么 DOWN,MOVE,UP事件都是交給 onInterceptTouchEvent 處理可上下滾動
  2. 如果在最后返回 true,那么 onInterceptTouchEvent 只會接受到一個 DOWN,一個 MOVE;但是onTouchEvent 接收到剩下的 MOVE 和 UP; 不可上下滾動
  3. 如果最后返回 false,但是在 onInterceptTouchEvent 的 DOWN 判斷中返回 true,這種情況同1
  4. 如果最后返回 false 或者 true,但是在 onInterceptTouchEvent 的 DOWN 判斷中調(diào)用rv.setLayoutFrozen(true);方法,那么 onInterceptTouchEvent 只會收到一個 DOWN
  5. 如果在最后返回 false,但是在 onInterceptTouchEvent 的 MOVE 判斷中 return true;的話,同情況2
  • 那么通過上面的預(yù)備知識和結(jié)論,我們實現(xiàn)的滑動刪除的思路也就漸漸清晰了:
  1. 最關(guān)鍵的是如何判斷應(yīng)該是 Item 的橫向滑動還是 RecyclerView 的上下滑動,這里可以通過判斷手指滑動的速度來判斷: 即在 onInterceptTouchEvent 方法中的 MOVE 事件中去判斷,如果 x 向速度大于 y 向速度,那么可以判斷為是 Item 的橫向滑動,直接 return true 即可,正如上面分析的那樣,之后直接在 onTouchEvent 方法中處理 Item 的滑動邏輯即可;這里還有一點需要注意的是,在 onInterceptTouchEvent 的 MOVE 事件中判斷時,對于一個完整的 DOWN->MOVE...MOVE->UP 過程,其實只需要,也只能執(zhí)行一次判斷,因為對于這樣一個完整的過程,一旦在初始 MOVE 中將該過程判斷為 Item 左右滑動或者 RecyclerView 上下滑動之后,中間就不可能突然改變,這對應(yīng)上面需要處理的細(xì)節(jié)中的情況5;所以這里筆者是通過一個標(biāo)志變量(flag)來實現(xiàn)的,需要注意的是在 UP 之后需要把 flag 置位,方便下一次判斷
  2. 對于當(dāng)手指 DOWN 時,已經(jīng)有了一個 Item 處于打開狀態(tài),那么此時也應(yīng)該分情況,當(dāng)此時手指 DOWN 處仍然為該打開 Item 時,那么手指的移動情況就應(yīng)該交給該 Item 來處理;如果此時手指 DOWN 的位置不是該打開 Item ,那么合理的處理是先關(guān)閉該 Item,之后在該過程中的 MOVE 事件還要不要響應(yīng),其實筆者覺得都是可以接受的;至于具體的細(xì)節(jié)處理是設(shè)置兩個 ViewHolder 變量來記錄(curHolder和oldHolder)即可,可在 onInterceptTouchEvent 中的 DOWN 事件中判斷
  3. 至于 Item 的平滑滑動和添加各種動畫之類的,讀者可以自行決定,這個不是本文的重點

三. 一個可擴(kuò)展的Demo

  • 這里給出筆者實現(xiàn)的一個完整 Demo,代碼中也有部分注釋,可以結(jié)合本文再來理清一下邏輯
  • 完整Demo代碼可以到筆者Github下載
  • 同時,讀者也可以根據(jù)自己的實際需要,重新設(shè)置布局和重新添加一些自己的滑動邏輯;需要需要解釋的是,這里筆者為了實現(xiàn)平滑移動,所以繼承了 RelativeLayout 在實現(xiàn)了一個 MyRelativeLayout 類,即最外層布局,如下可知,筆者只是簡單的在其中使用了一個 Scroller 類來實現(xiàn)平滑移動,其他也沒有復(fù)雜的操作
  • public class MyRelativeLayout extends RelativeLayout {

      private Scroller scroller;
    
      public MyRelativeLayout(Context context) {
          super(context);
          init(context);
      }
    
      public MyRelativeLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
          init(context);
      }
    
      public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context);
      }
    
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          init(context);
      }
    
      private void init(Context context) {
          scroller = new Scroller(context);
      }
    
      public void onScroll(int dx) {
          if (this.getScrollX() != 0) {
              scroller.startScroll(this.getScrollX(), 0, dx, 0);
              invalidate();
          }
      }
    
      @Override
      public void computeScroll() {
          super.computeScroll();
          if (scroller.computeScrollOffset()) {
              this.scrollTo(scroller.getCurrX(), 0);
              invalidate();
          }
      }
    

    }

  • 最后,老規(guī)矩,看一下實現(xiàn)效果


    move.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鄙漏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖线脚,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刻像,死亡現(xiàn)場離奇詭異,居然都是意外死亡同仆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進(jìn)店門裙品,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俗批,“玉大人,你說我怎么就攤上這事市怎∷晖” “怎么了?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵区匠,是天一觀的道長干像。 經(jīng)常有香客問我,道長驰弄,這世上最難降的妖魔是什么麻汰? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮戚篙,結(jié)果婚禮上五鲫,老公的妹妹穿的比我還像新娘。我一直安慰自己岔擂,他們只是感情好位喂,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著智亮,像睡著了一般忆某。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阔蛉,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天弃舒,我揣著相機(jī)與錄音,去河邊找鬼状原。 笑死聋呢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颠区。 我是一名探鬼主播削锰,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毕莱!你這毒婦竟也來了器贩?” 一聲冷哼從身側(cè)響起颅夺,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛹稍,沒想到半個月后吧黄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡唆姐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年拗慨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奉芦。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡赵抢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出声功,到底是詐尸還是另有隱情烦却,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布先巴,位于F島的核電站短绸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏筹裕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一窄驹、第九天 我趴在偏房一處隱蔽的房頂上張望朝卒。 院中可真熱鬧,春花似錦乐埠、人聲如沸抗斤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑞眼。三九已至,卻和暖如春棵逊,著一層夾襖步出監(jiān)牢的瞬間伤疙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工辆影, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留徒像,地道東北人。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓蛙讥,卻偏偏與公主長得像锯蛀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子次慢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

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