Android自定義滑動(dòng)刪除效果的實(shí)現(xiàn)代碼
先給大家展示下效果圖,如果感覺(jué)不錯(cuò),請(qǐng)參考實(shí)現(xiàn)代碼:

序言
最近項(xiàng)目中需要用到滑動(dòng)刪除,然后去網(wǎng)上搜了一下,發(fā)現(xiàn)現(xiàn)有網(wǎng)上的各種解決辦法各式各樣,但是還是找不到一個(gè)能將所有細(xì)節(jié)和邏輯處理好的,至于滑動(dòng)刪除部分,我覺(jué)得處理的相對(duì)比較好的是 QQ(包括處理各種邏輯和細(xì)節(jié));最終,苦尋無(wú)果,于是決定自己動(dòng)手,豐衣足食
這篇文章將從現(xiàn)有 Android 滑動(dòng)刪除的痛點(diǎn),到搭建好一個(gè)基本的框架,到最終提供一份完整的 Demo為止,爭(zhēng)取為讀者提供最大的可定制化
正文
一. 滑動(dòng)刪除的痛點(diǎn)
(1). 現(xiàn)有資料中的不足
筆者參閱了網(wǎng)上的一些博客,發(fā)現(xiàn),這些博客中大多能夠基本實(shí)現(xiàn)滑動(dòng)刪除,但是存在的問(wèn)題是,對(duì)于面向用戶(hù)實(shí)際使用而言,卻是遠(yuǎn)遠(yuǎn)不夠的大多數(shù)博客實(shí)現(xiàn)的只是當(dāng)手指 DOWN 的時(shí)候,通過(guò)判斷左右滑動(dòng)和上下滑動(dòng)的距離之比來(lái)判斷 Item 是否應(yīng)該滑動(dòng);但是有一個(gè)問(wèn)題就是,用戶(hù) DOWN 的時(shí)候獲得焦點(diǎn)的 Item ,但是 MOVE 的時(shí)候手指離開(kāi)了該 Item 的時(shí)候應(yīng)該如何處理呢? 按照正常的用戶(hù)邏輯,這時(shí)仍然應(yīng)該是該 Item 處理滑動(dòng)事件最重要和最難的部分當(dāng)然也是滑動(dòng)沖突了,即不管使用 RecyclerView 還是使用 ListView 實(shí)現(xiàn),其都存在處理上下滑動(dòng)和左右滑動(dòng)的沖突問(wèn)題,很明顯的是我們不能一味地?cái)r截所有事件,因?yàn)閷?duì)于上下滑動(dòng)事件還需要交給 RecyclerView/ListView 來(lái)實(shí)現(xiàn)正常的上下滑動(dòng);滑動(dòng)沖突部分如果處理不好的話(huà)會(huì)出現(xiàn)很明顯的卡頓現(xiàn)象,同時(shí)也會(huì)出現(xiàn)不符合用戶(hù)心理預(yù)期的響應(yīng),而這些都是用戶(hù)不友好的
另外,現(xiàn)有的資料都是在自己的代碼實(shí)現(xiàn)上講解的,對(duì)于實(shí)現(xiàn)正真的定制化還是很有難度的,當(dāng)我們想要實(shí)現(xiàn)自己想要的功能時(shí),我們還需要去看懂一些不相關(guān)的處理邏輯
(2). 需要處理的細(xì)節(jié)
我一直覺(jué)得 QQ 在處理滑動(dòng)刪除上做的是相對(duì)比較好的,特別是從各種細(xì)節(jié)處理上,它基本上都能給出符合用戶(hù)心理預(yù)期的響應(yīng),這里也是以 QQ 為例來(lái)介紹幾種需要注意和處理的細(xì)節(jié);當(dāng)然,需要注意的地方很多,一一例舉不太現(xiàn)實(shí),具體的還是需要自己動(dòng)手啦
側(cè)滑過(guò)程中,DOWN 時(shí)得到焦點(diǎn)的 Item 在 MOVE 過(guò)程中失去了焦點(diǎn)應(yīng)該怎么處理?(即對(duì)應(yīng)上面的 現(xiàn)有資料中的不足 中的第2項(xiàng));如下圖所示,手指 DOWN 的時(shí)候得到焦點(diǎn)的是 Item 7, 但是之后手指在 MOVE 過(guò)程中,Item 7 失去了焦點(diǎn);正如上面所說(shuō),此時(shí)還是應(yīng)該交由該 Item 7 處理滑動(dòng)事件(如果在 DOWN 的時(shí)候已經(jīng)判為側(cè)滑的話(huà))
如果當(dāng)前有 Item 正在側(cè)滑,那么 RecyclerView 就不能再同時(shí)上下滑動(dòng)
如果當(dāng)前有 Item 處于打開(kāi)狀態(tài),那么在下一次 DOWN 的時(shí)候應(yīng)該先將其關(guān)閉,同時(shí)在 UP 之前,MOVE 事件都應(yīng)該是無(wú)效的(對(duì)于這種情況,也可以按照自己的邏輯處理,如: 如果當(dāng)前有 Item 處于打開(kāi)狀態(tài),那么在下一次 DOWN 的時(shí)候應(yīng)該先將其關(guān)閉,但是在關(guān)閉之后,在 UP 之前出現(xiàn)的 MOVE 事件也應(yīng)該響應(yīng))
在一次 DOWN->MOVE...MOVE->UP 的完整過(guò)程中,一旦初始判斷決定了應(yīng)該是上下滑動(dòng)或者 Item 的左右滑動(dòng)之后,在 MOVE 過(guò)程中就不能改變,直至下一次新的判斷過(guò)程為止(這種情況容易出現(xiàn)在用戶(hù)在一次過(guò)程中反復(fù)的上下滑動(dòng)時(shí)突然來(lái)一次左右滑動(dòng)(或者反復(fù)的左右滑動(dòng)過(guò)程中,突然來(lái)一次上下滑動(dòng)))
二. 一個(gè)框架
(1). 使用 RecyclerView 搭建框架
1. 預(yù)備知識(shí)
RecyclerView 對(duì)外提供的接口已經(jīng)比較完善,所以不需要再去繼承 RecyclerView 來(lái)監(jiān)聽(tīng)其 MotionEvent 事件
可以通過(guò) RecyclerView 的 addOnItemTouchListener() 方法來(lái)實(shí)現(xiàn)對(duì)所有 MotionEvent 的攔截,其需要傳入一個(gè) RecyclerView.OnItemTouchListener 對(duì)象,這是一個(gè) interface ,需要我們自己來(lái)實(shí)現(xiàn)邏輯,這里筆者寫(xiě)了一個(gè)大致的 Demo 先來(lái)看看其各個(gè)方法之間的聯(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í)行測(cè)試;這里筆者就直接給出在 onInterceptTouchEvent 方法中返回不同值時(shí)的結(jié)論了:
如果在最后返回 false,那么 DOWN,MOVE,UP事件都是交給 onInterceptTouchEvent 處理可上下滾動(dòng)
如果在最后返回 true,那么 onInterceptTouchEvent 只會(huì)接受到一個(gè) DOWN,一個(gè) MOVE;但是onTouchEvent 接收到剩下的 MOVE 和 UP; 不可上下滾動(dòng)
如果最后返回 false,但是在 onInterceptTouchEvent 的 DOWN 判斷中返回 true,這種情況同1
如果最后返回 false 或者 true,但是在 onInterceptTouchEvent 的 DOWN 判斷中調(diào)用rv.setLayoutFrozen(true);方法,那么 onInterceptTouchEvent 只會(huì)收到一個(gè) DOWN
如果在最后返回 false,但是在 onInterceptTouchEvent 的 MOVE 判斷中 return true;的話(huà),同情況2
那么通過(guò)上面的預(yù)備知識(shí)和結(jié)論,我們實(shí)現(xiàn)的滑動(dòng)刪除的思路也就漸漸清晰了:
最關(guān)鍵的是如何判斷應(yīng)該是 Item 的橫向滑動(dòng)還是 RecyclerView 的上下滑動(dòng),這里可以通過(guò)判斷手指滑動(dòng)的速度來(lái)判斷: 即在 onInterceptTouchEvent 方法中的 MOVE 事件中去判斷,如果 x 向速度大于 y 向速度,那么可以判斷為是 Item 的橫向滑動(dòng),直接 return true 即可,正如上面分析的那樣,之后直接在 onTouchEvent 方法中處理 Item 的滑動(dòng)邏輯即可;這里還有一點(diǎn)需要注意的是,在 onInterceptTouchEvent 的 MOVE 事件中判斷時(shí),對(duì)于一個(gè)完整的 DOWN->MOVE...MOVE->UP 過(guò)程,其實(shí)只需要,也只能執(zhí)行一次判斷,因?yàn)閷?duì)于這樣一個(gè)完整的過(guò)程,一旦在初始 MOVE 中將該過(guò)程判斷為 Item 左右滑動(dòng)或者 RecyclerView 上下滑動(dòng)之后,中間就不可能突然改變,這對(duì)應(yīng)上面 需要處理的細(xì)節(jié) 中的情況5;所以這里筆者是通過(guò)一個(gè)標(biāo)志變量(flag)來(lái)實(shí)現(xiàn)的,需要注意的是在 UP 之后需要把 flag 置位,方便下一次判斷
對(duì)于當(dāng)手指 DOWN 時(shí),已經(jīng)有了一個(gè) Item 處于打開(kāi)狀態(tài),那么此時(shí)也應(yīng)該分情況,當(dāng)此時(shí)手指 DOWN 處仍然為該打開(kāi) Item 時(shí),那么手指的移動(dòng)情況就應(yīng)該交給該 Item 來(lái)處理;如果此時(shí)手指 DOWN 的位置不是該打開(kāi) Item ,那么合理的處理是先關(guān)閉該 Item,之后在該過(guò)程中的 MOVE 事件還要不要響應(yīng),其實(shí)筆者覺(jué)得都是可以接受的;至于具體的細(xì)節(jié)處理是設(shè)置兩個(gè) ViewHolder 變量來(lái)記錄(curHolder和oldHolder)即可,可在 onInterceptTouchEvent 中的 DOWN 事件中判斷
至于 Item 的平滑滑動(dòng)和添加各種動(dòng)畫(huà)之類(lèi)的,讀者可以自行決定,這個(gè)不是本文的重點(diǎn)
三. 一個(gè)可擴(kuò)展的Demo
這里給出筆者實(shí)現(xiàn)的一個(gè)完整 Demo,代碼中也有部分注釋,可以結(jié)合本文再來(lái)理清一下邏輯
完整Demo代碼可以到筆者 Github 下載
同時(shí),讀者也可以根據(jù)自己的實(shí)際需要,重新設(shè)置布局和重新添加一些自己的滑動(dòng)邏輯;需要需要解釋的是,這里筆者為了實(shí)現(xiàn)平滑移動(dòng),所以繼承了 RelativeLayout 在實(shí)現(xiàn)了一個(gè) MyRelativeLayout 類(lèi),即最外層布局,如下可知,筆者只是簡(jiǎn)單的在其中使用了一個(gè) Scroller 類(lèi)來(lái)實(shí)現(xiàn)平滑移動(dòng),其他也沒(méi)有復(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();
}
}
}
總結(jié)
以上所述是小編給大家介紹的Android自定義滑動(dòng)刪除效果的實(shí)現(xiàn)代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android Parcleable接口的調(diào)用源碼層分析
這篇文章主要給大家介紹了關(guān)于利用Kotlin如何實(shí)現(xiàn)Android開(kāi)發(fā)中的Parcelable的相關(guān)資料,并且給大家介紹了關(guān)于Android Parcleable源碼層問(wèn)題,需要的朋友可以參考下2022-12-12
詳解Android App卸載后跳轉(zhuǎn)到指定的反饋?lái)?yè)面的方法
這篇文章主要介紹了Android App卸載后跳轉(zhuǎn)到指定的反饋?lái)?yè)面的方法,關(guān)鍵點(diǎn)是相關(guān)線(xiàn)程要判斷在目錄被消除以前作出響應(yīng),需要的朋友可以參考下2016-04-04
Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法
這篇文章主要介紹了Android中AutoCompleteTextView與MultiAutoCompleteTextView的用法,需要的朋友可以參考下2014-07-07
Android studio中生成引用.aar和.jar的方法詳解
這篇文章主要是講解.aar的生成與引用,文中的內(nèi)容屬于完全基礎(chǔ)性概念,對(duì)剛學(xué)習(xí)使用Android studio的朋友們很有幫助,有需要的可以參考學(xué)習(xí),下面來(lái)一起看看吧。2016-09-09
Android EditText被軟鍵盤(pán)遮蓋的處理方法
android app新增了透明欄效果,結(jié)果發(fā)現(xiàn)鍵盤(pán)彈起后會(huì)遮蓋屏幕底部的EditText,沒(méi)有像想象中的調(diào)整窗口大小,并滾動(dòng)ScrollView,將EditText顯示在鍵盤(pán)上方。下面小編把解決方法記錄一下,特此分享到腳本之家平臺(tái),感興趣的朋友一起看看吧2016-10-10
Android-自定義控件之ListView下拉刷新的實(shí)現(xiàn)
本篇文章主要介紹了Android-自定義控件之ListView下拉刷新示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Android WebView實(shí)現(xiàn)全屏播放視頻
WebView是Android系統(tǒng)中的原生控件,其主要功能與前端頁(yè)面進(jìn)行響應(yīng)交互,快捷省時(shí)地實(shí)現(xiàn)如期的功能,相當(dāng)于增強(qiáng)版的內(nèi)置瀏覽器。這篇文章主要介紹的是利用WebView實(shí)現(xiàn)全屏播放視頻的功能,感興趣的小伙伴可以了解一下2021-12-12
Android四大組件之Service服務(wù)詳細(xì)講解
Android的服務(wù)是開(kāi)發(fā)Android應(yīng)用程序的重要組成部分。不同于活動(dòng)Activity,服務(wù)是在后臺(tái)運(yùn)行,服務(wù)沒(méi)有接口,生命周期也與活動(dòng)Activity非常不同。通過(guò)使用服務(wù)我們可以實(shí)現(xiàn)一些后臺(tái)操作,比如想從遠(yuǎn)程服務(wù)器加載一個(gè)網(wǎng)頁(yè)等,下面來(lái)看看詳細(xì)內(nèi)容,需要的朋友可以參考下2022-07-07

