仿微信侧滑删除

先上效果图:



代码实现如下:

recyclerview添加如下ItemTouchListener

class ItemTouchLis : RecyclerView.OnItemTouchListener {
    private var targetView: ViewGroup? = null//当前操作的view
    private var lastView: ViewGroup? = null//上一次操作的view
    private val animTime = 200L//动画时间
    private var isDragging = false//是否处于拖拽状态
    private var downPoint = PointF(0f, 0f)//按下的点,用于判断位移距离
    private var downScrollX = 0//按下时view的已滑动距离
    private val offsetDistance = ViewConfiguration.get(MyApp.instance).scaledTouchSlop//最小触发侧滑的距离
    private var secondItemWidth = 0//由于第二项要伸缩,固定取第一次获取的宽度
    private var fixedOffset = 0f//未读和删除两项总宽度之和,初始化同上
    private var isVerticalScroll = false//recyclerview上下滑动
    private var isExpanedWhenActionDown = false
    private var isTouchOriginalArea = false//是否触发原来item的点击事件(展开状态下,直接关闭侧滑菜单,不触发点击事件
    private var isTouchConfirm = false//判断点击区域位置
    private var downTime = 0L//按下的时间(用于判断是否触发点击事件
    private var isExpanded = false//删除按钮是否展开状态
    override fun onInterceptTouchEvent(rv: RecyclerView?, ev: MotionEvent?): Boolean {
        if (ev == null || rv == null) return false
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                downTime = System.currentTimeMillis()
                isVerticalScroll = false
                isDragging = false
                isTouchConfirm = false
                if (isTouchOriginalArea) {
                    lastView = null
                    isTouchOriginalArea = false
                }
                isExpanedWhenActionDown = isExpanded
                downPoint.set(ev.x, ev.y)
                lastView = targetView
                targetView = rv.findChildViewUnder(ev.x, ev.y) as ViewGroup?
                val targetView = targetView ?: return false
                downScrollX = targetView.scrollX
                val childCount = targetView.childCount
                if (fixedOffset == 0f) {
                    if (childCount > 1) {
                        (1 until childCount).forEach {
                            val child = targetView.getChildAt(it)
                            if (it == 2 && secondItemWidth == 0) {
                                secondItemWidth = child.width
                            }
                            val lp = child.layoutParams as ViewGroup.MarginLayoutParams
                            fixedOffset += child.width + lp.leftMargin - child.paddingRight
                        }
                    }
                }
                //恢复上一个view状态
                if (lastView != null && lastView != targetView) {
                    resetView(lastView!!)
                    lastView = null
                    isExpanded = false
                    isExpanedWhenActionDown = false
                    //判断当前view点击状态
                } else if (lastView == targetView && targetView.scrollX != 0) {
                    val w = targetView.width
                    val pre = targetView.getChildAt(1)
                    var firstItemWidth = pre.width - pre.paddingRight
                    //点击删除,确认删除之前状态
                    if (targetView.scrollX + ev.x > w + firstItemWidth) {
                        if (!isExpanded) {
                            isTouchConfirm = true
                            switch(targetView, true)
                            return true
                        }
                        //点击item显示区域
                    } else if (targetView.scrollX + ev.x < w) {
                        resetView(targetView)
                        isTouchOriginalArea = true
                        return true
                    }
                }
            }
            MotionEvent.ACTION_MOVE -> {
                //判断是否可拖拽
                drag(ev)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if(isTouchOriginalArea){
                    return true
                }
                var needResetTarget = true
                val targetView = targetView ?: return false
                if (targetView.scrollX != 0) {
                    val w = targetView.width
                    //触发点击事件
                    if (System.currentTimeMillis() - downTime < ViewConfiguration.getTapTimeout() && !isDragging) {
                        val pre = targetView.getChildAt(1)
                        val firstItemWidth = pre.width - pre.paddingRight
                        //点击删除区域
                        if (targetView.scrollX + ev.x > w + firstItemWidth) {
                            //判断是否确认删除状态
                            if (isExpanded) {
                                if (!isExpanedWhenActionDown) {
                                    needResetTarget = false
                                }
                            } else {
                                switch(targetView, true)
                                return true
                            }
                            //点击非item区域
                        } else if (targetView.scrollX + ev.x > w) {
                            //判断是否删除状态
                            if (isExpanded) {
                                if (!isExpanedWhenActionDown) {
                                    needResetTarget = false
                                }
                            } else {
                                resetView(targetView)
                                needResetTarget = false
                            }
                        }
                    }
                }
                if (needResetTarget) {
                    resetView(targetView, isExpanded)
                }
            }
            else -> {
            }
        }
        return isDragging || isTouchOriginalArea || isTouchConfirm
    }

    override fun onTouchEvent(rv: RecyclerView?, event: MotionEvent?) {
        if (rv == null || event == null || isTouchOriginalArea || isTouchConfirm) {
            return
        }
        val targetView = targetView ?: return
        when (event.action) {
            MotionEvent.ACTION_MOVE -> {
                if (isExpanded) {
                    return
                }
                var distance = downPoint.x - event.x + downScrollX
                if (distance < 0f) {
                    distance = 0f
                }
                var rate = distance / fixedOffset
                val childCount = targetView.childCount
                if (childCount > 2) {
                    (2 until childCount).forEach {
                        val pre = targetView.getChildAt(it - 1)
                        val translationX = (pre.width - pre.paddingRight) * rate
                        val view = targetView.getChildAt(it)
                        view.translationX = translationX
                    }
                }
                targetView.scrollTo(distance.toInt(), 0)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                var needResetTarget = true
                if (targetView.scrollX != 0) {
                    val w = targetView.width
                    //触发点击事件
                    if (System.currentTimeMillis() - downTime < ViewConfiguration.getTapTimeout() && !isDragging) {
                        val pre = targetView.getChildAt(1)
                        val firstItemWidth = pre.width - pre.paddingRight
                        //点击删除区域
                        if (targetView.scrollX + event.x > w + firstItemWidth) {
                            //判断是否确认删除状态
                            if (isExpanded) {
                                if (!isExpanedWhenActionDown) {
                                    needResetTarget = false
                                }
                            } else {
                                switch(targetView, true)
                                return
                            }
                            //点击非item区域
                        } else if (targetView.scrollX + event.x > w) {
                            //判断是否删除状态
                            if (isExpanded) {
                                if (!isExpanedWhenActionDown) {
                                    needResetTarget = false
                                }
                            } else {
                                resetView(targetView)
                                needResetTarget = false
                            }
                        }
                    }
                }
                if (needResetTarget) {
                    resetView(targetView, isExpanded)
                }
            }
            else -> {
            }
        }
    }

    private fun drag(ev: MotionEvent) {
        if (isVerticalScroll) {
            return
        }
        val x = Math.abs(downPoint.x - ev.x)
        val y = Math.abs(downPoint.y - ev.y)
        if (y > offsetDistance && y > x) {
            isVerticalScroll = true
            return
        }
        if (x > offsetDistance) {
            isDragging = true
        }
    }

    private fun resetView(viewGroup: ViewGroup, toZero: Boolean = true) {
        log("resetView---toZero=$toZero")
        val count = viewGroup.childCount
        if (count <= 2) {
            viewGroup.scrollTo(0, 0)
            return
        }
        if (toZero) {
            scrollToZero(viewGroup)
        } else {
            if (viewGroup.scrollX > fixedOffset / 2) {
                scrollToMax(viewGroup)
            } else {
                scrollToZero(viewGroup)
            }
        }
    }

    private fun switch(vg: ViewGroup, expand: Boolean) {
        log("switch")
        isExpanded = true
        if (vg.scrollX != fixedOffset.toInt()) {
            vg.scrollTo(fixedOffset.toInt(), 0)
        }
        val pre = vg.getChildAt(1)
        val firstItemWidth = pre.width - pre.paddingRight
        val view = vg.getChildAt(2)
        if (view is TextView) {
            view.text = vg.context.string(R.string.confirm_delete)
        }
        val anim = ObjectAnimator.ofFloat(view, "translationX", view.translationX, 0f).setDuration(animTime)
        anim.addUpdateListener {
            val wd = (1 - view.translationX / firstItemWidth) * firstItemWidth
            val lp = view.layoutParams
            lp.width = wd.toInt() + secondItemWidth
            view.layoutParams = lp
        }
        anim.start()
    }


    fun scrollToZero(viewGroup: ViewGroup) {
        log("scrollToZero")
        isExpanded = false
        if (viewGroup.scrollX != 0) {
            val anim = ObjectAnimator.ofInt(viewGroup, "scrollX", viewGroup.scrollX, 0).setDuration(animTime)
            anim.interpolator = DecelerateInterpolator()
            anim.addListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }

                override fun onAnimationEnd(animation: Animator?) {
                    val view = viewGroup.getChildAt(2)
                    if (view is TextView) {
                        view.text = view.context.string(R.string.delete)
                    }
                    val lp = view.layoutParams
                    if (view.translationX != 0f) {
                        view.translationX = 0f
                    }
                    if (lp.width != secondItemWidth) {
                        lp.width = secondItemWidth
                        view.layoutParams = lp
                    }
                }

                override fun onAnimationCancel(animation: Animator?) {
                }

                override fun onAnimationStart(animation: Animator?) {
                }

            })
            anim.start()
        }
    }

    private fun scrollToMax(viewGroup: ViewGroup) {
        log("scrollToMax")
        val anim = ObjectAnimator.ofInt(viewGroup, "scrollX", viewGroup.scrollX, fixedOffset.toInt()).setDuration(animTime)
        anim.interpolator = OvershootInterpolator(3f)
        val view = viewGroup.getChildAt(2)
        val pre = viewGroup.getChildAt(1)
        val preW = pre.width - pre.paddingRight
        anim.addUpdateListener {
            var rate = viewGroup.scrollX / fixedOffset
            view.translationX = rate * preW
        }
        anim.start()
    }


    override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
    }
}

然后再Item布局外用如下FrameLayout嵌套,完全和frameLayout相同,只是改了第一个之后的子view的位置(只做了两个item的,多个item暂未适配(之后会考虑改为一个通用控件)

class SwipeDeleteLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
    private val screenWidth = context.screenWidth

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        (0 until childCount).forEach {
            val child = getChildAt(it)
            if (it == 0) {
                child.layout(0, 0, context.screenWidth, bottom - top)
            } else {
                child.layout(screenWidth, 0, child.measuredWidth + screenWidth, bottom - top)
            }
        }
    }
}

用到了属性动画,用的时候记得自行处理一下生命周期的问题

布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<SwipeDeleteLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dimen_100">

    <your_layout />
<android.support.text.emoji.widget.EmojiTextView
    android:id="@+id/read_status"
    android:layout_width="@dimen/dimen_416"
    android:layout_height="match_parent"
    android:background="@color/color_CECECE"
    android:gravity="center"
    android:paddingRight="@dimen/dimen_300"
    android:textColor="@color/color_FFFFFF"
    android:textStyle="bold" />

<android.support.text.emoji.widget.EmojiTextView
    android:id="@+id/delete"
    android:layout_width="@dimen/dimen_382"
    android:layout_height="match_parent"
    android:background="@color/color_FF0458"
    android:gravity="center"
    android:paddingRight="@dimen/dimen_300"
    android:text="@string/delete"
    android:textColor="@color/color_FFFFFF"
    android:textStyle="bold" />
</SwipeDeleteLayout>


  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值