Android implements a RecyclerView with a left slide delete button

Keywords: Android Mobile less

Android implements a RecyclerView with a left slide delete button

Train of thought

To achieve such an effect, it is mainly to distribute and sort out the events of recyclerview, transfer them horizontally to view, vertically to recyclerview, and click to transfer the events intact

Reference effect

It mainly refers to the message effect of mobile qq. Simply speaking, slide the delete button to the left and press any other position to restore.

Realization

It's very troublesome to split the specific ideas. It's easy to paste the code directly. Basically, it's almost like commenting

package com.jkys.common_ui_widget

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.OvershootInterpolator

class LeftWipeRecyclerView : RecyclerView {
    //The reason why Scroller is not used for scrolling animation is that Scroller needs to be used with ondraw method, while I only need to use child view scroll
    var scroller: ValueAnimator

    var touchSlop = 0 //The smallest sliding unit beyond which an effective sliding system value is considered

    constructor(context: Context?) : this(context, null)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
        //Define animation for rebound effect
        scroller = ValueAnimator()
        scroller.setInterpolator(OvershootInterpolator(5f))
        layoutManager = LinearLayoutManager(context)

        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    override fun requestLayout() {
        super.requestLayout()
        if (mSwipeTargetView != null) {
            mSwipeTargetView?.scrollTo(0, 0)
            mSwipeTargetView = null
            isFirstCheckMoved = false
            isShouldCheckTouch = true
        }
    }

    override fun setLayoutManager(layout: LayoutManager?) {
        if (layout is LinearLayoutManager) {
            super.setLayoutManager(layout)
        } else {
            super.setLayoutManager(LinearLayoutManager(context))

        }
    }


    var speed = 1 // Rebound velocity coefficient

    //Whether horizontal movement has been carried out
    //That is to say, based on this, whether the touch event up should be passed to the child to trigger the click
    var isHorScrolled = false

    //Sliding View
    var mSwipeTargetView: View? = null

    //Judge whether it is the first time to receive a valid touch Move
    //Avoid strange sliding
    var isFirstCheckMoved: Boolean = false

    //If you scroll in the y direction, event distribution is not handled,
    // Handed over to normal RecyclerView for processing
    var isShouldCheckTouch: Boolean = true

    var lastPos = FloatArray(2)//Record the coordinates of the last touch point

    var scrollRange = 0 //Record sliding range


    var isClickOtherItem = false //Whether other positions are clicked when the hidden area is displayed


    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        //If you do not accept any touch events in a rolling animation
        if (isScrolling) return true

        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                //down event initialization
                isClickOtherItem = false
                isHorScrolled = false
                isFirstCheckMoved=false
                lastPos[0] = ev?.x
                lastPos[1] = ev?.y

                //Judge if there is a sliding area display, at this time, touch
                // The leaked View is valid
                //Intercept other View touch events and roll the exposed View back to its original state
                if (mSwipeTargetView != null) {
                    var clickRect = Rect()
                    mSwipeTargetView!!.getHitRect(clickRect)

                    if (!clickRect.contains(ev?.x.toInt(), ev?.y.toInt())) {
                        scrollToIdle(true)
                        isFirstCheckMoved = false
                        isShouldCheckTouch = true
                        //isClickOtherItem set to true to intercept event delivery
                        //{@link #onInterceptTouchEvent}
                        isClickOtherItem = true

                    }
                    return super.dispatchTouchEvent(ev)

                }


            }
            MotionEvent.ACTION_MOVE -> {
                if (isShouldCheckTouch) {
                    //Check if it is the minimum unit of scrolling
                    if (Math.hypot(ev.x.toDouble() - lastPos[0], ev.y.toDouble() - lastPos[1]) > touchSlop) {
                        //Is it the first time to scroll
                        //In this way, it can avoid the strange judgment and View state caused by sliding the fingers left and right while the recylerview itself is rolling
                        if (!isFirstCheckMoved) {
                            //If the conditions are met, check whether it is x-direction or y-direction rolling
                            if (checkScollHorizon(ev.x, ev.y)) {
                                //The first roll is horizontal
                                //Then find and assign the current slide View
                                if (mSwipeTargetView == null) {
                                    mSwipeTargetView = findItemByPostion(lastPos[0].toInt(), lastPos[1].toInt())
                                }
                            }
                            isFirstCheckMoved = true
                        }

                        if (mSwipeTargetView != null) {
                            //If the horizontal sliding exceeds a certain level, the next click event will not occur
                            if (Math.abs(ev.x.toDouble() - lastPos[0]) > touchSlop) {
                                isHorScrolled = true
                            }

                            if (scrollHor(ev)) {
                                return true
                            }
                        } else {
                            //If there is no sliding target, the move event will not be processed,
                            //Give the recyclerview parent class method to handle the event distribution
                            lastPos[0] = ev.x
                            lastPos[1] = ev.y
                            isShouldCheckTouch = false
                        }
                    } else {
//                        The sliding distance is less than the critical value of sliding judgment
//                        In order to effect the process, satisfy the x direction, and the sliding view is not Null, slide horizontally
                        return scrollHor(ev)
                    }
                }
            }
            MotionEvent.ACTION_UP -> {

//                When loosened
                isShouldCheckTouch = true
                if (mSwipeTargetView != null) {

                    //Swipe view to scroll to the specified position
                    // Default OR show all hidden views
                    scrollToIdle()

                    //If the event is clicked horizontally, it will not be passed down
                    if (isHorScrolled) {
                        isHorScrolled = false
                        return true
                    }
                }


            }
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {

        // isClickOtherItem block event delivery
        return super.onInterceptTouchEvent(e) || isClickOtherItem
    }


    //When there is a sliding view, the sliding view follows the finger movement. Note that
    // Finger slide left, ev?. X - lastpos [0] < 0
    // To display the area on the right, scrollx should be > 0
    //So m swipetargetview!!. Scrollx - scrollvalue
    //That is, slide left to show right
    private fun scrollHor(ev: MotionEvent?): Boolean {
        if (ev == null) return false
        if (mSwipeTargetView != null) {

            if (checkScollHorizon(ev?.x, ev?.y)) {
                var scrollvalue = (ev?.x - lastPos[0]).toInt()
                var scrolltarget = mSwipeTargetView!!.scrollX - scrollvalue
                if (scrolltarget >= scrollRange) {
                    scrolltarget = scrollRange
                } else if (scrolltarget < 0) {
                    scrolltarget = 0
                }
                mSwipeTargetView?.scrollTo(scrolltarget, 0)
            }
            lastPos[0] = ev?.x
            lastPos[1] = ev?.y
        }
        return true
    }

    //Check whether it is in x direction, and judge whether it is in x direction twice as much as in y direction
    fun checkScollHorizon(x: Float, y: Float): Boolean {
        return Math.abs(y - lastPos[1]) * 2 < Math.abs(x - lastPos[0])
    }

    //Find the clicked view
    //Traversing all child ren's heights
    //Find the view meeting the coordinates in it
    fun findItemByPostion(x: Int, y: Int): View? {


        var firstchildIndex = (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
        if (firstchildIndex < 0) {
            return null
        }

        val count = getChildCount();
        val itemRect = Rect()
        for (i in 0 until count) {
            val child = getChildAt(i);
            if (child != null && child.getVisibility() == View.VISIBLE) {
                child.getHitRect(itemRect);
                if (itemRect.contains(x, y)) {

                    //viewholderitem needs to indicate that the ID of the hidden area is R.id.scrollable, so as to locate the width of the hidden area
                    scrollRange = child.findViewById<View>(R.id.scrollable).width

                    return child
                }
            }
        }
        return null
    }


    private var isScrolling: Boolean = false

    //Let go of the sliding view or click another area to scroll to the appropriate position
    //Please note that if you are dragging back to the original position, manually release the view
    fun scrollToIdle(forceBase: Boolean = false) {
        if (mSwipeTargetView != null) {
            var isBackToZero = forceBase || scrollRange > mSwipeTargetView!!.scrollX * 2
            var scrollTarget = if (isBackToZero) 0 else scrollRange
            if (mSwipeTargetView!!.scrollX == scrollTarget) {
                if (scrollTarget == 0) {
                    mSwipeTargetView = null
                }
                return
            }
            scroller.setIntValues(mSwipeTargetView!!.scrollX, scrollTarget)
            scroller.addUpdateListener {
                mSwipeTargetView?.scrollTo(it.animatedValue as Int, 0)
            }
            scroller.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    mSwipeTargetView?.scrollTo(scrollTarget, 0)
                    if (scrollTarget == 0) {
                        mSwipeTargetView = null
                    }
                    scroller?.removeAllUpdateListeners()
                    scroller?.removeAllListeners()
                    isScrolling = false
                }
            })
            scroller.setDuration(Math.abs(scrollRange) / 2 * speed.toLong())
            scroller.start()
            isScrolling = true
        }
    }

    //Release resources
    override fun onDetachedFromWindow() {
        mSwipeTargetView=null
        scroller?.cancel()
        scroller?.removeAllUpdateListeners()
        scroller?.removeAllListeners()
        super.onDetachedFromWindow()

    }


}

Use

If you want to use it, please remember that the slide out area of the layout of the item needs id= R.id.scrollable. It's just as good as the ordinary recyclerView, and you don't need to write in detail

Posted by EviL_CodE on Sat, 23 Nov 2019 10:53:37 -0800