Android 基于RecyclerView的Item側(cè)滑刪除

RecyclerView的強(qiáng)大之處就不用多說(shuō)了,誰(shuí)用誰(shuí)知道哦,本著學(xué)習(xí)的態(tài)度我們來(lái)給RecyclerView加上側(cè)滑刪除Item的功能,話不多說(shuō),先看圖:


ItemRemoveRecyclerView

Gif效果不夠理想,嗚嗚......

其實(shí)核心思想很簡(jiǎn)單,就是通過(guò)重寫(xiě)RecyclerView的onTouchEvent()方法來(lái)檢測(cè)手勢(shì)的變化實(shí)現(xiàn)的,大致的流程如下:
1、根據(jù)手指觸摸的坐標(biāo)點(diǎn)找到對(duì)應(yīng)Item的ViewHolder,進(jìn)而得到相應(yīng)的Item布局View。
2、手指繼續(xù)移動(dòng),在條件滿足的情況下,通過(guò)scrollBy()使Item布局View內(nèi)容跟隨手指一起移動(dòng),當(dāng)然要注意邊界檢測(cè)。
3、手指抬起時(shí),根據(jù)Item布局View內(nèi)容移動(dòng)的距離以及手指的滑動(dòng)速度,判斷是否顯示刪除按鈕,進(jìn)而通過(guò)startScroll()使Item布局View自動(dòng)滑動(dòng)到目標(biāo)位置。
4、點(diǎn)擊刪除按鈕則刪除對(duì)應(yīng)Item,點(diǎn)擊其它區(qū)域則隱藏刪除按鈕。

由于Item的側(cè)滑刪除效果需要通過(guò)Scroller輔助實(shí)現(xiàn)的,還不了解Scroller的同學(xué)可以看下這篇文章:Android Scroller實(shí)現(xiàn)View彈性滑動(dòng)完全解析。

接下來(lái)看一下具體的實(shí)現(xiàn)過(guò)程:
先看一下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ī)定刪除按鈕有四個(gè)狀態(tài)(mDeleteBtnState):0:關(guān)閉,1:將要關(guān)閉,2:將要打開(kāi),3:打開(kāi)
當(dāng)刪除按鈕未展示時(shí),即if (mDeleteBtnState == 0)時(shí),通過(guò)findChildViewUnder()方法得到觸摸點(diǎn)對(duì)應(yīng)的Item View,接下來(lái)通過(guò)getChildViewHolder()得到對(duì)應(yīng)的ViewHolder,有了ViewHolder,我們就可以解析出Item的布局mItemLayout以及當(dāng)前Item的下標(biāo)mPosition,最后得到mMaxLength ,即刪除按鈕的寬度也就是Item的最大滑動(dòng)距離,同時(shí)給刪除按鈕綁定事件。
當(dāng)
else if (mDeleteBtnState == 3)
時(shí),Item上的刪除按鈕完全展示,如果點(diǎn)擊刪除按鈕外的任意區(qū)域則通過(guò)startScroll()方法使Item自動(dòng)右滑直到刪除按鈕完全隱藏,并且onTouchEvent()方法返回flase,這樣此次事件結(jié)束,不會(huì)繼續(xù)傳遞。
如果前兩個(gè)條件都不滿足,表示上一次Item的滑動(dòng)操作尚未結(jié)束,則直接返回false,保證上一次的滑動(dòng)操作順利完成。

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) {//左邊界檢測(cè)
                        mItemLayout.scrollTo(0, 0);
                        return true;
                    } else if (scrollX + dx >= mMaxLength) {//右邊界檢測(cè)
                        mItemLayout.scrollTo(mMaxLength, 0);
                        return true;
                    }
                    mItemLayout.scrollBy(dx, 0);//item跟隨手指滑動(dòng)
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.onTouchEvent(e);
    }

當(dāng)手指滑動(dòng)的時(shí)候,如果水平滑動(dòng)距離大于垂直滑動(dòng)距離,則通過(guò)scrollBy()方法使Item可跟隨手指左右滑動(dòng),當(dāng)然我們進(jìn)行了滑動(dòng)的邊界檢測(cè),并不會(huì)出現(xiàn)滑動(dòng)越界的情況哦!

最后看一下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ì)算手指滑動(dòng)的速度
                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的左滑動(dòng)距離大于刪除按鈕寬度的一半,則則顯示刪除按鈕
                        deltaX = mMaxLength - upScrollX;
                        mDeleteBtnState = 2;
                    } else if (upScrollX < mMaxLength / 2) {//否則隱藏
                        deltaX = -upScrollX;
                        mDeleteBtnState = 1;
                    }
                }

                //item自動(dòng)滑動(dòng)到指定位置
                mScroller.startScroll(upScrollX, 0, deltaX, 0, 200);
                isStartScroll = true;
                invalidate();

                mVelocityTracker.clear();
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.onTouchEvent(e);
    }

當(dāng)手指抬起時(shí),如果之前沒(méi)有發(fā)生Item水平滑動(dòng)、上下滑動(dòng)列表、回調(diào)接口不為空,則認(rèn)為是Item的點(diǎn)擊事件,執(zhí)行回調(diào)接口里的方法mListener.onItemClick(mItemLayout, mPosition);。接下來(lái)計(jì)算出手指在水平以及垂直方向的滑動(dòng)速度**xVelocity 、yVelocity ,如果if (Math.abs(xVelocity) > 100 && Math.abs(xVelocity) > Math.abs(yVelocity)),則根據(jù)速度判斷手指抬起后Item的滑動(dòng)情況,if (xVelocity <= -100)代表左滑速度大于等于100,則將mDeleteBtnState值改為2,代表刪除按鈕將要打開(kāi)(展示),同理如果右滑速度大于100則刪除按鈕將要關(guān)閉(隱藏),同時(shí)計(jì)算出相應(yīng)的滑動(dòng)距離deltaX **。如果不滿足通過(guò)速度的判斷條件則根據(jù)Item的滑動(dòng)距離來(lái)判斷,如果if (upScrollX >= mMaxLength / 2),即Item左滑的距離大于等于刪除按鈕寬度的一半,則將mDeleteBtnState值改為2,否則將mDeleteBtnState值改為1,同時(shí)不要忘了計(jì)算deltaX的值。最后通過(guò)mScroller.startScroll(upScrollX, 0, deltaX, 0, 200);使Item滑動(dòng)到指定位置。

到這里我們的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自動(dòng)滑動(dòng)的狀態(tài),在MotionEvent.ACTION_DOWN我們將其賦值為true,代表開(kāi)始自動(dòng)滑動(dòng),如果自動(dòng)滑動(dòng)結(jié)束則會(huì)執(zhí)行else if中的邏輯,重置isStartScroll、修改mDeleteBtnState最終的狀態(tài)值(打開(kāi)或者關(guān)閉)。

手指上下滑動(dòng)列表時(shí),我們通過(guò)onScrollStateChanged()方法,監(jiān)聽(tīng)列表滑動(dòng)的狀態(tài):

public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        isDragging = state == SCROLL_STATE_DRAGGING;
    }

以判斷是否正在上下滑動(dòng)列表。

到此,我們把大致的實(shí)現(xiàn)方法就分析完了,如有不合理的地方歡迎指出?。?!如有興趣可下載源碼看看:點(diǎn)我下載哦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,048評(píng)論 25 709
  • 什么是View View 是 Android 中所有控件的基類。 View的位置參數(shù) View 的位置由它的四個(gè)頂...
    acc8226閱讀 1,385評(píng)論 0 7
  • 12.24深圳南山區(qū)聚橙劇院趙雷我們的時(shí)光劇場(chǎng)巡回演唱會(huì) 剛開(kāi)始知道雷子要來(lái)深圳的時(shí)候我并不準(zhǔn)備去看,原因是因?yàn)闆](méi)...
    初念于你閱讀 509評(píng)論 0 0
  • 青草綠樹(shù)紅花。 臺(tái)榭高樓民家。 洛城熱浪人煞。 殘陽(yáng)西斜, 好男兒懷天下。
    江郎舞風(fēng)閱讀 275評(píng)論 0 0
  • 無(wú)淚空等倪紅亂情, 曠野無(wú)痕花草凋零。 天冷清清卻無(wú)悲風(fēng), 你不等因緣盡數(shù)斷。 你不聽(tīng)前后因果冥。 颼颼颯颯起了風(fēng)。
    姓梁心不涼閱讀 402評(píng)論 0 0

友情鏈接更多精彩內(nèi)容