亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android自定義view實現(xiàn)左滑刪除的RecyclerView詳解

 更新時間:2022年11月03日 08:31:03   作者:撿一晌貪歡  
RecyclerView是Android一個更強大的控件,其不僅可以實現(xiàn)和ListView同樣的效果,還有優(yōu)化了ListView中的各種不足。其可以實現(xiàn)數(shù)據(jù)縱向滾動,也可以實現(xiàn)橫向滾動(ListView做不到橫向滾動)。接下來講解RecyclerView的用法

概述

最近安卓自定義view的知識看的很熟,但是卻很久沒動手了,這幾天用kotlin手撕了原先一個左滑刪除的RecyclerView,居然弄得有點懵逼。后面又慢慢改進、加?xùn)|西,發(fā)現(xiàn)這樣一個例子下來,自定義View以及事件分發(fā)的知識居然覆蓋的差不多了,所以有了寫博客的想法。下面我會從我的思路一點點的寫下去,碰到的各種問題就是知識的實際應(yīng)用了,通過問題學(xué)知識,我覺得這樣的方式非常好!

需求

這里我要做的是一個左滑刪除列表項的功能,之前拿過一個別人的用,所以有了一點思路,但是不深刻。于是我開始從零出發(fā),先寫個大致思路再一步步去解決,首先肯定的是通過繼承RecyclerView去實現(xiàn),后面思路大致如下:

  • 在 down 事件中,判斷在列表內(nèi)位置,得到對應(yīng) item
  • 攔截 move 事件,item 跟隨滑動,最大距離為刪除按鈕長度
  • 在 up 事件中,確定最終狀態(tài),固定 item 位置

編寫代碼I

根據(jù)上面三點思路,我刷刷地就寫下了下面的代碼:

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流暢滑動
    private var mScroller = Scroller(context)
    //當(dāng)前選中item
    private var mItem: ViewGroup? = null
    //上次按下橫坐標(biāo)
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
           when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //獲取點擊位置
                   getSelectItem(e)
                   //設(shè)置點擊的橫坐標(biāo)
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //不管左右都應(yīng)該讓item跟隨滑動
                   moveItem(e)
                   //攔截事件
                   return true
               }
               MotionEvent.ACTION_UP -> {
                   //判斷結(jié)果
                   stopMove(e)
               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    //滑動結(jié)束
    //版本一:判斷一下結(jié)束的位置,補充或恢復(fù)位置
    private fun stopMove(e: MotionEvent) {
        mItem?.let {
            val dx = e.x - mLastX
            //如果移動過半了,應(yīng)該判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(dx) >= deleteWidth / 2) {
                //觸發(fā)移動
                val left = if (dx > 0) {
                    deleteWidth - dx
                }else {
                    - deleteWidth + dx
                }
                mScroller.startScroll(0, 0, left.toInt(),0)
                invalidate()
            }else {
                //如果移動沒過半應(yīng)該恢復(fù)狀態(tài)
                mScroller.startScroll(0, 0, - dx.toInt(),0)
                invalidate()
            }
            //清除狀態(tài)
            mLastX = 0f
            mItem = null
        }
    }
    //移動item
    //版本一:絕對值小于刪除按鈕長度隨便移動,大于則不移動
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = e.x - mLastX
            //這里默認(rèn)最后一個view是刪除按鈕
            if (abs(dx) < it.getChildAt(it.childCount - 1).width) {
                //觸發(fā)移動
                mScroller.startScroll(0, 0, dx.toInt(), 0)
                invalidate()
            }
        }
    }
    //獲取點擊位置
    //版本一:通過點擊的y坐標(biāo)除于item高度得出
    private fun getSelectItem(e: MotionEvent) {
        val firstChild = getChildAt(0)
        firstChild?.let {
            val pos = (e.x / firstChild.height).toInt()
            mItem = getChildAt(pos) as ViewGroup
        }
    }
    //流暢地滑動
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollBy(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

注意啊,這里的代碼是沒法用的,滑動后選不中正確的item,距離也有問題,所以里面有很多問題!

kotlin的構(gòu)造

其實上來最懵逼的就是kotlin的構(gòu)造函數(shù),自己寫了幾次,感覺都不對,還是搜了下,有兩種寫法,我還是覺得使用JvmOverloads的比較方便,不過好像在API版本>21時還有個defStyleRes,我這就不相敘了,可以查資料。

獲取的item位置不對

這里獲取的item明顯不對,其實這個問題很好發(fā)現(xiàn),因為事件的x是屏幕的x啊,這里使用列表去計算明顯不行,而且考慮了可見性嗎?考慮可滑動隱藏了嗎?考慮了第一個item子顯示部分嗎?

結(jié)合上面這些問題,應(yīng)該如何去正確獲取item的位置呢?看下面代碼:

    private fun getSelectItem(e: MotionEvent) {
        val frame = Rect()
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }

這里參考了別人的代碼,通過遍歷子item,檢查事件坐標(biāo)是否在其中,在的話得到選中的item,不再需要position了,還是挺好理解的。

移動的計算不對

上面的代碼將mLastX只記錄down事件,而每次的是事件和dwon事件橫坐標(biāo)差值,明顯錯了。

首先mLastX這里應(yīng)該記錄的是每個事件的x,包含move的事件,移動的差值應(yīng)該是一個小的差值。

MotionEvent.ACTION_MOVE -> {
    //移動控件
    moveItem(e)
    //更新點擊的橫坐標(biāo)
    mLastX = e.x
    //攔截事件
    return true
}
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = mLastX - e.x
            //檢查mItem移動后應(yīng)該在[-deleteLength, 0]內(nèi)
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX + dx) <= deleteWidth && (it.scrollX + dx) >= 0) {
                //觸發(fā)移動
                it.scrollBy(dx.toInt(), 0)
            }
        }
    }

滑動結(jié)束結(jié)束判斷不對

上面的mLastX修改后,滑動結(jié)束結(jié)束的判斷不對,而且原本就是不對的哈!mScroller的移動就錯了,正確的看下面:

    private fun stopMove() {
        mItem?.let {
            //如果移動過半了,應(yīng)該判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2) {
                //觸發(fā)移動至完全展開
                mScroller.startScroll(it.scrollX, 0, - deleteWidth,0)
                invalidate()
            }else {
                //如果移動沒過半應(yīng)該恢復(fù)狀態(tài)
                mScroller.startScroll(it.scrollX, 0, 0,0)
                invalidate()
            }
            //清除狀態(tài)
            mLastX = 0f
            mItem = null
        }
    }

編寫代碼II

改完上面代碼大致就有了第二版,下面看全部代碼,看看還有什么問題啊:

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流暢滑動
    private var mScroller = Scroller(context)
    //當(dāng)前選中item
    private var mItem: ViewGroup? = null
    //上次按下橫坐標(biāo)
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //獲取點擊位置
                   getSelectItem(e)
                   //設(shè)置點擊的橫坐標(biāo)
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //移動控件
                   moveItem(e)
                   //更新點擊的橫坐標(biāo)
                   mLastX = e.x
                   //攔截事件
                   return true
               }
               MotionEvent.ACTION_UP -> {
                   //判斷結(jié)果
                   stopMove()
               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    //滑動結(jié)束
    //版本一:判斷一下結(jié)束的位置,補充或恢復(fù)位置
    //問題:mLast不應(yīng)該是down的位置
    //版本二:
    private fun stopMove() {
        mItem?.let {
            //如果移動過半了,應(yīng)該判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2) {
                //觸發(fā)移動至完全展開
                mScroller.startScroll(it.scrollX, 0, - deleteWidth,0)
                invalidate()
            }else {
                //如果移動沒過半應(yīng)該恢復(fù)狀態(tài)
                mScroller.startScroll(it.scrollX, 0, 0,0)
                invalidate()
            }
            //清除狀態(tài)
            mLastX = 0f
            mItem = null
        }
    }
    //移動item
    //版本一:絕對值小于刪除按鈕長度隨便移動,大于則不移動
    //問題:移動方向反了,而且左右可以滑動,沒有限定住范圍,mLast只是記住down的位置
    //版本二:通過整體移動的數(shù)值,和每次更新的數(shù)值,判斷是否在范圍內(nèi),再移動
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = mLastX - e.x
            //檢查mItem移動后應(yīng)該在[-deleteLength, 0]內(nèi)
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX + dx) <= deleteWidth && (it.scrollX + dx) >= 0) {
                //觸發(fā)移動
                it.scrollBy(dx.toInt(), 0)
            }
        }
    }
    //獲取點擊位置
    //版本一:通過點擊的y坐標(biāo)除于item高度得出
    //問題:沒考慮列表項的可見性、列表滑動的情況,并且x和屏幕有關(guān)不僅僅是列表
    //版本二:通過遍歷子view檢查事件在哪個view內(nèi),得到點擊的item
    private fun getSelectItem(e: MotionEvent) {
        //獲得第一個可見的item的position
        val frame = Rect()
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流暢地滑動
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollBy(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

代碼改完,運行,誒,怎么只能滑動一小下?打斷點試一下,選中的item正確了,但是怎么ACTION_MOVE只觸發(fā)一次?怎么ACTION_UP不觸發(fā)呢?這里就要注意下ACTION_MOVE里的代碼:

MotionEvent.ACTION_MOVE -> {
    //移動控件
    moveItem(e)
    //更新點擊的橫坐標(biāo)
    mLastX = e.x
    //攔截事件
    return true
}

這里返回了true?攔截事件?那后續(xù)的一系列事件不就是被當(dāng)前view攔截了嗎?果然僅僅一個onInterceptTouchEvent是搞不定的?。?/p>

其實這里還有一個隱藏問題,computeScroll里面真的寫對了嗎?scrollBy和scrollTo有了解嗎?

下面看再次改進的代碼,主要就是改的上面兩點,改動篇幅有點大,就全貼出來了。

編寫代碼III

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流暢滑動
    private var mScroller = Scroller(context)
    //當(dāng)前選中item
    private var mItem: ViewGroup? = null
    //上次按下橫坐標(biāo)
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //獲取點擊位置
                   getSelectItem(e)
                   //設(shè)置點擊的橫坐標(biāo)
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //判斷是否攔截
                   return moveItem(e)
               }
//               MotionEvent.ACTION_UP -> {
//                   //判斷結(jié)果
//                   stopMove()
//               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
                //攔截了ACTION_MOVE后,后面一系列event都會交到本view處理
                MotionEvent.ACTION_MOVE -> {
                    //移動控件
                    moveItem(e)
                    //更新點擊的橫坐標(biāo)
                    mLastX = e.x
                }
                MotionEvent.ACTION_UP -> {
                    //判斷結(jié)果
                    stopMove()
                }
            }
        }
        return super.onTouchEvent(e)
    }
    //滑動結(jié)束
    //版本一:判斷一下結(jié)束的位置,補充或恢復(fù)位置
    //問題:mLast不應(yīng)該是down的位置
    //版本二:改進結(jié)果判斷
    //問題:onInterceptTouchEvent的ACTION_UP不觸發(fā)
    //版本三:改進補充或恢復(fù)位置的邏輯
    private fun stopMove() {
        mItem?.let {
            //如果移動過半了,應(yīng)該判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2f) {
                //觸發(fā)移動至完全展開
                mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX,0)
                invalidate()
            }else {
                //如果移動沒過半應(yīng)該恢復(fù)狀態(tài)
                mScroller.startScroll(it.scrollX, 0, -it.scrollX,0)
                invalidate()
            }
            //清除狀態(tài)
            mLastX = 0f
            //不能為null,后續(xù)流暢滑動要用到
            //mItem = null
        }
    }
    //移動item
    //版本一:絕對值小于刪除按鈕長度隨便移動,大于則不移動
    //問題:移動方向反了,而且左右可以滑動,沒有限定住范圍,mLast只是記住down的位置
    //版本二:通過整體移動的數(shù)值,和每次更新的數(shù)值,判斷是否在范圍內(nèi),再移動
    //問題:onInterceptTouchEvent的ACTION_MOVE只觸發(fā)一次
    //版本三:放在onTouchEvent內(nèi)執(zhí)行,并且在onInterceptTouchEvent給出一個攔截判斷
    private fun moveItem(e: MotionEvent): Boolean {
        mItem?.let {
            val dx = mLastX - e.x
            //檢查mItem移動后應(yīng)該在[-deleteLength, 0]內(nèi)
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX + dx) <= deleteWidth && (it.scrollX + dx) >= 0) {
                //觸發(fā)移動
                it.scrollBy(dx.toInt(), 0)
                return true
            }
        }
        return false
    }
    //獲取點擊位置
    //版本一:通過點擊的y坐標(biāo)除于item高度得出
    //問題:沒考慮列表項的可見性、列表滑動的情況,并且x和屏幕有關(guān)不僅僅是列表
    //版本二:通過遍歷子view檢查事件在哪個view內(nèi),得到點擊的item
    //問題:沒有問題,成功拿到了mItem
    private fun getSelectItem(e: MotionEvent) {
        //獲得第一個可見的item的position
        val frame = Rect()
        //防止點擊其他地方,保持上一個item
        mItem = null
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流暢地滑動
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

把上面代碼運行下,果然就十分完美了,可是是不是覺得沒徹底搞定啊?別急下面我們再加點東西.

優(yōu)化

優(yōu)化一:TouchSlop

TouchSlop是一個移動的最小距離,由系統(tǒng)提供,可以用它來判斷一個滑動距離是否有效。

優(yōu)化二:VelocityTracker

VelocityTracker是一個速度計算的工具,由native提供,可以計算移動像素點的速度,我們可以利用它判斷當(dāng)滑動速度很快時也展開刪除按鈕。

優(yōu)化三:GestureDetector

GestureDetector是手勢控制類,可以很方便的判斷各種手勢,我們這可以設(shè)計它雙擊展開刪除按鈕。

優(yōu)化后代碼

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //系統(tǒng)最小移動距離
    private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
    //最小有效速度
    private val mMinVelocity = 600
    //增加手勢控制,雙擊快速完成側(cè)滑,還是為了練習(xí)
    private var isDoubleClick = false
    private var mGestureDetector: GestureDetector
        = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener(){
            override fun onDoubleTap(e: MotionEvent?): Boolean {
                e?.let { event->
                    getSelectItem(event)
                    mItem?.let {
                        val deleteWidth = it.getChildAt(it.childCount - 1).width
                        //觸發(fā)移動至完全展開deleteWidth
                        if (it.scrollX == 0) {
                            mScroller.startScroll(0, 0, deleteWidth, 0)
                        }else {
                            mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
                        }
                        isDoubleClick = true
                        invalidate()
                        return true
                    }
                }
                //不進行攔截,只是作為工具判斷下雙擊
                return false
            }
        })
    //使用速度控制器,增加側(cè)滑速度判定滑動成功,主要為了是練習(xí)
    //VelocityTracker 由 native 實現(xiàn),需要及時釋放內(nèi)存
    private var mVelocityTracker: VelocityTracker? = null
    //流暢滑動
    private var mScroller = Scroller(context)
    //當(dāng)前選中item
    private var mItem: ViewGroup? = null
    //上次事件的橫坐標(biāo)
    private var mLastX = 0f
    //當(dāng)前RecyclerView被上層viewGroup分發(fā)到事件,所有事件都會通過dispatchTouchEvent給到
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        //
        mGestureDetector.onTouchEvent(ev)
        return super.dispatchTouchEvent(ev)
    }
    //viewGroup對子控件的事件攔截,一旦攔截,后續(xù)事件序列不會再調(diào)用onInterceptTouchEvent
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when (e.action) {
                MotionEvent.ACTION_DOWN -> {
                    //這里的優(yōu)化會阻止雙擊滑動的使用,實際也沒什么好優(yōu)化的
//                    //防止快速按下情況出問題
//                    if (!mScroller.isFinished) {
//                        mScroller.abortAnimation()
//                    }
                    //獲取點擊位置
                    getSelectItem(e)
                    //設(shè)置點擊的橫坐標(biāo)
                    mLastX = e.x
                }
                MotionEvent.ACTION_MOVE -> {
                    //判斷是否攔截
                    //如果攔截了ACTION_MOVE,后續(xù)事件就不觸發(fā)onInterceptTouchEvent了
                    return moveItem(e)
                }
                //攔截了ACTION_MOVE,ACTION_UP也不會觸發(fā)
//                MotionEvent.ACTION_UP -> {
//                    //判斷結(jié)果
//                    stopMove()
//                }
            }
        }
        return super.onInterceptTouchEvent(e)
    }
    //攔截后對事件的處理,或者子控件不處理,返回到父控件處理,在onTouch之后,在onClick之前
    //如果不消耗,則在同一事件序列中,當(dāng)前View無法再次接受事件
    //performClick會被onTouchEvent攔截,我們這不需要點擊,全都交給super實現(xiàn)去了
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when (e.action) {
                //沒有攔截,也不能攔截,所以不需要處理
//                MotionEvent.ACTION_DOWN -> {}
                //攔截了ACTION_MOVE后,后面一系列event都會交到本view處理
                MotionEvent.ACTION_MOVE -> {
                    //移動控件
                    moveItem(e)
                    //更新點擊的橫坐標(biāo)
                    mLastX = e.x
                }
                MotionEvent.ACTION_UP -> {
                    //判斷結(jié)果
                    stopMove()
                }
            }
        }
        return super.onTouchEvent(e)
    }
    //滑動結(jié)束
    //版本一:判斷一下結(jié)束的位置,補充或恢復(fù)位置
    //問題:mLast不應(yīng)該是down的位置
    //版本二:改進結(jié)果判斷
    //問題:onInterceptTouchEvent的ACTION_UP不觸發(fā)
    //版本三:改進補充或恢復(fù)位置的邏輯
    private fun stopMove() {
        mItem?.let {
            //如果移動過半了,應(yīng)該判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            //如果整個移動過程速度大于600,也判定滑動成功
            //注意如果沒有攔截ACTION_MOVE,mVelocityTracker是沒有初始化的
            var velocity = 0f
            mVelocityTracker?.let { tracker ->
                tracker.computeCurrentVelocity(1000)
                velocity = tracker.xVelocity
            }
            //判斷結(jié)束情況,移動過半或者向左速度很快都展開
            if ( (abs(it.scrollX) >= deleteWidth / 2f) || (velocity < - mMinVelocity) ) {
                //觸發(fā)移動至完全展開
                mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX, 0)
                invalidate()
            }else {
                //如果移動沒過半應(yīng)該恢復(fù)狀態(tài),或者向右移動很快則恢復(fù)到原來狀態(tài)
                mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
                invalidate()
            }
            //清除狀態(tài)
            mLastX = 0f
            //不能為null,后續(xù)mScroller要用到
            //mItem = null
            //mVelocityTracker由native實現(xiàn),需要及時釋放
            mVelocityTracker?.apply {
                clear()
                recycle()
            }
            mVelocityTracker = null
        }
    }
    //移動item
    //版本一:絕對值小于刪除按鈕長度隨便移動,大于則不移動
    //問題:移動方向反了,而且左右可以滑動,沒有限定住范圍,mLast只是記住down的位置
    //版本二:通過整體移動的數(shù)值,和每次更新的數(shù)值,判斷是否在范圍內(nèi),再移動
    //問題:onInterceptTouchEvent的ACTION_MOVE只觸發(fā)一次
    //版本三:放在onTouchEvent內(nèi)執(zhí)行,并且在onInterceptTouchEvent給出一個攔截判斷
    @SuppressLint("Recycle")
    private fun moveItem(e: MotionEvent): Boolean {
        mItem?.let {
            val dx = mLastX - e.x
            //最小的移動距離應(yīng)該舍棄,onInterceptTouchEvent不攔截,onTouchEvent內(nèi)才更新mLastX
            if(abs(dx) > mTouchSlop) {
                //檢查mItem移動后應(yīng)該在[-deleteLength, 0]內(nèi)
                val deleteWidth = it.getChildAt(it.childCount - 1).width
                if ((it.scrollX + dx) <= deleteWidth && (it.scrollX + dx) >= 0) {
                    //觸發(fā)移動
                    it.scrollBy(dx.toInt(), 0)
                    //觸發(fā)速度計算
                    //這里Recycle不存在問題,一旦返回true,就會攔截事件,就會到達(dá)ACTION_UP去回收
                    mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                    mVelocityTracker!!.addMovement(e)
                    return true
                }
            }
        }
        return false
    }
    //獲取點擊位置
    //版本一:通過點擊的y坐標(biāo)除于item高度得出
    //問題:沒考慮列表項的可見性、列表滑動的情況,并且x和屏幕有關(guān)不僅僅是列表
    //版本二:通過遍歷子view檢查事件在哪個view內(nèi),得到點擊的item
    //問題:沒有問題,成功拿到了mItem
    private fun getSelectItem(e: MotionEvent) {
        //獲得第一個可見的item的position
        val frame = Rect()
        //防止點擊其他地方,保持上一個item
        mItem = null
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流暢地滑動
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

TouchSlop、VelocityTracker和GestureDetector的用法都很簡單,但是有一點必須得說一下,那就是在dispatchTouchEvent中傳遞事件給GestureDetector,為什么呢?因為onInterceptTouchEvent攔截后就搜不到事件了,onTouchEvent的執(zhí)行和自身及子控件有關(guān),有不確定性,只有dispatchTouchEvent中的事件一定會收到!

總結(jié)

一篇文章下來,代碼貼的有點多了,篇幅很長,但是如果仔細(xì)品的話,你會發(fā)現(xiàn)從事件分發(fā)到攔截都從問題里面學(xué)到了,幾種滑動方式以及滑動的相對性也涉及了,坐標(biāo)系也有了一定理解,其他幾個工具TouchSlop、VelocityTracker和GestureDetector都用到了,還算可以吧!

到此這篇關(guān)于Android自定義view實現(xiàn)左滑刪除的RecyclerView詳解的文章就介紹到這了,更多相關(guān)Android RecyclerView內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論