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