RecyclerView的強(qiáng)大之處就不用多說了茅逮,誰用誰知道哦憔购,本著學(xué)習(xí)的態(tài)度我們來給RecyclerView加上側(cè)滑刪除Item的功能焦匈,話不多說赘淮,先看圖:
Gif效果不夠理想辕录,嗚嗚......
其實(shí)核心思想很簡單,就是通過重寫RecyclerView的onTouchEvent()方法來檢測手勢的變化實(shí)現(xiàn)的梢卸,大致的流程如下:
1走诞、根據(jù)手指觸摸的坐標(biāo)點(diǎn)找到對應(yīng)Item的ViewHolder,進(jìn)而得到相應(yīng)的Item布局View蛤高。
2蚣旱、手指繼續(xù)移動,在條件滿足的情況下戴陡,通過scrollBy()使Item布局View內(nèi)容跟隨手指一起移動塞绿,當(dāng)然要注意邊界檢測。
3恤批、手指抬起時异吻,根據(jù)Item布局View內(nèi)容移動的距離以及手指的滑動速度,判斷是否顯示刪除按鈕喜庞,進(jìn)而通過startScroll()使Item布局View自動滑動到目標(biāo)位置诀浪。
4棋返、點(diǎn)擊刪除按鈕則刪除對應(yīng)Item,點(diǎn)擊其它區(qū)域則隱藏刪除按鈕笋妥。
由于Item的側(cè)滑刪除效果需要通過Scroller輔助實(shí)現(xiàn)的懊昨,還不了解Scroller的同學(xué)可以看下這篇文章:Android Scroller實(shí)現(xiàn)View彈性滑動完全解析。
接下來看一下具體的實(shí)現(xiàn)過程:
先看一下onTouchEvent的MotionEvent.ACTION_DOWN事件處理:
public boolean onTouchEvent(MotionEvent e) {
mVelocityTracker.addMovement(e);
int x = (int) e.getX();
int y = (int) e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mDeleteBtnState == 0) {
View view = findChildViewUnder(x, y);
if (view == null) {
return false;
}
MyViewHolder viewHolder = (MyViewHolder) getChildViewHolder(view);
mItemLayout = viewHolder.layout;
mPosition = viewHolder.getAdapterPosition();
mDelete = (TextView) mItemLayout.findViewById(R.id.item_delete);
mMaxLength = mDelete.getWidth();
mDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onDeleteClick(mPosition);
mItemLayout.scrollTo(0, 0);
mDeleteBtnState = 0;
}
});
} else if (mDeleteBtnState == 3) {
mScroller.startScroll(mItemLayout.getScrollX(), 0, -mMaxLength, 0, 200);
invalidate();
mDeleteBtnState = 0;
return false;
} else {
return false;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return super.onTouchEvent(e);
}
我們規(guī)定刪除按鈕有四個狀態(tài)(mDeleteBtnState):0:關(guān)閉春宣,1:將要關(guān)閉酵颁,2:將要打開,3:打開
當(dāng)刪除按鈕未展示時月帝,即if (mDeleteBtnState == 0)
時躏惋,通過findChildViewUnder()方法得到觸摸點(diǎn)對應(yīng)的Item View,接下來通過getChildViewHolder()得到對應(yīng)的ViewHolder嚷辅,有了ViewHolder簿姨,我們就可以解析出Item的布局mItemLayout以及當(dāng)前Item的下標(biāo)mPosition,最后得到mMaxLength 簸搞,即刪除按鈕的寬度也就是Item的最大滑動距離扁位,同時給刪除按鈕綁定事件。
當(dāng)else if (mDeleteBtnState == 3)時趁俊,Item上的刪除按鈕完全展示域仇,如果點(diǎn)擊刪除按鈕外的任意區(qū)域則通過startScroll()方法使Item自動右滑直到刪除按鈕完全隱藏,并且onTouchEvent()方法返回flase寺擂,這樣此次事件結(jié)束暇务,不會繼續(xù)傳遞。
如果前兩個條件都不滿足怔软,表示上一次Item的滑動操作尚未結(jié)束垦细,則直接返回false,保證上一次的滑動操作順利完成挡逼。
onTouchEvent的MotionEvent.ACTION_MOVE事件處理代碼如下:
public boolean onTouchEvent(MotionEvent e) {
mVelocityTracker.addMovement(e);
int x = (int) e.getX();
int y = (int) e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int dx = mLastX - x;
int dy = mLastY - y;
int scrollX = mItemLayout.getScrollX();
if (Math.abs(dx) > Math.abs(dy)) {
isItemMoving = true;
if (scrollX + dx <= 0) {//左邊界檢測
mItemLayout.scrollTo(0, 0);
return true;
} else if (scrollX + dx >= mMaxLength) {//右邊界檢測
mItemLayout.scrollTo(mMaxLength, 0);
return true;
}
mItemLayout.scrollBy(dx, 0);//item跟隨手指滑動
}
break;
case MotionEvent.ACTION_UP:
break;
}
mLastX = x;
mLastY = y;
return super.onTouchEvent(e);
}
當(dāng)手指滑動的時候括改,如果水平滑動距離大于垂直滑動距離,則通過scrollBy()方法使Item可跟隨手指左右滑動家坎,當(dāng)然我們進(jìn)行了滑動的邊界檢測嘱能,并不會出現(xiàn)滑動越界的情況哦!
最后看一下onTouchEvent的MotionEvent.ACTION_UP事件處理:
public boolean onTouchEvent(MotionEvent e) {
mVelocityTracker.addMovement(e);
int x = (int) e.getX();
int y = (int) e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (!isItemMoving && !isDragging && mListener != null) {
mListener.onItemClick(mItemLayout, mPosition);
}
isItemMoving = false;
mVelocityTracker.computeCurrentVelocity(1000);//計(jì)算手指滑動的速度
float xVelocity = mVelocityTracker.getXVelocity();//水平方向速度(向左為負(fù))
float yVelocity = mVelocityTracker.getYVelocity();//垂直方向速度
int deltaX = 0;
int upScrollX = mItemLayout.getScrollX();
if (Math.abs(xVelocity) > 100 && Math.abs(xVelocity) > Math.abs(yVelocity)) {
if (xVelocity <= -100) {//左滑速度大于100乘盖,則刪除按鈕顯示
deltaX = mMaxLength - upScrollX;
mDeleteBtnState = 2;
} else if (xVelocity > 100) {//右滑速度大于100,則刪除按鈕隱藏
deltaX = -upScrollX;
mDeleteBtnState = 1;
}
} else {
if (upScrollX >= mMaxLength / 2) {//item的左滑動距離大于刪除按鈕寬度的一半憔涉,則則顯示刪除按鈕
deltaX = mMaxLength - upScrollX;
mDeleteBtnState = 2;
} else if (upScrollX < mMaxLength / 2) {//否則隱藏
deltaX = -upScrollX;
mDeleteBtnState = 1;
}
}
//item自動滑動到指定位置
mScroller.startScroll(upScrollX, 0, deltaX, 0, 200);
isStartScroll = true;
invalidate();
mVelocityTracker.clear();
break;
}
mLastX = x;
mLastY = y;
return super.onTouchEvent(e);
}
當(dāng)手指抬起時订框,如果之前沒有發(fā)生Item水平滑動、上下滑動列表兜叨、回調(diào)接口不為空穿扳,則認(rèn)為是Item的點(diǎn)擊事件衩侥,執(zhí)行回調(diào)接口里的方法mListener.onItemClick(mItemLayout, mPosition);
。接下來計(jì)算出手指在水平以及垂直方向的滑動速度**xVelocity 矛物、yVelocity 茫死,如果if (Math.abs(xVelocity) > 100 && Math.abs(xVelocity) > Math.abs(yVelocity))
,則根據(jù)速度判斷手指抬起后Item的滑動情況履羞,if (xVelocity <= -100)
代表左滑速度大于等于100峦萎,則將mDeleteBtnState值改為2,代表刪除按鈕將要打開(展示)忆首,同理如果右滑速度大于100則刪除按鈕將要關(guān)閉(隱藏)爱榔,同時計(jì)算出相應(yīng)的滑動距離deltaX **。如果不滿足通過速度的判斷條件則根據(jù)Item的滑動距離來判斷糙及,如果if (upScrollX >= mMaxLength / 2)
详幽,即Item左滑的距離大于等于刪除按鈕寬度的一半,則將mDeleteBtnState值改為2浸锨,否則將mDeleteBtnState值改為1唇聘,同時不要忘了計(jì)算deltaX的值。最后通過mScroller.startScroll(upScrollX, 0, deltaX, 0, 200);
使Item滑動到指定位置柱搜。
到這里我們的onTouchEvent()實(shí)現(xiàn)原理就分析完了迟郎。
再貼一下computeScroll()的代碼:
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
mItemLayout.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
} else if (isStartScroll) {
isStartScroll = false;
if (mDeleteBtnState == 1) {
mDeleteBtnState = 0;
}
if (mDeleteBtnState == 2) {
mDeleteBtnState = 3;
}
}
}
其中isStartScroll代表手指抬起后Item自動滑動的狀態(tài),在MotionEvent.ACTION_DOWN我們將其賦值為true冯凹,代表開始自動滑動谎亩,如果自動滑動結(jié)束則會執(zhí)行else if中的邏輯,重置isStartScroll宇姚、修改mDeleteBtnState最終的狀態(tài)值(打開或者關(guān)閉)匈庭。
手指上下滑動列表時,我們通過onScrollStateChanged()方法浑劳,監(jiān)聽列表滑動的狀態(tài):
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
isDragging = state == SCROLL_STATE_DRAGGING;
}
以判斷是否正在上下滑動列表阱持。
到此,我們把大致的實(shí)現(xiàn)方法就分析完了魔熏,如有不合理的地方歡迎指出V匝省!蒜绽!如有興趣可下載源碼看看:點(diǎn)我下載哦