1.引言
左滑删除item在现在的聊天软件中随处可见,最常见的就是和RecyclerView的Item绑定一起使用。接下来就一起开发一个简单的左滑删除自定义View。
2.话不多说直接上代码。
LeftDeleteView【左滑删除控件】
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.*
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.R
import kotlin.math.abs
/**
* Description:左滑删除按钮
*
* Time:2021/7/1-20:37
* Author:我叫PlusPlus
*/
class LeftDeleteView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
/**
* 上下文
*/
private val mContext = context
/**
* 最小触摸距离
*/
private var mMinTouchDistance : Int = 0
/**
* 右边可滑动距离
*/
private var mRightCanDistance :Int = 0
/**
* 按下时 x
*/
private var mInitX : Float = 0f
/**
* 按下y
*/
private var mInitY : Float = 0f
/**
* 属性动画
*/
private var mValueAnimator: ValueAnimator? = null
/**
* 动画时长
*/
private var mAnimDuring = 300
/**
* RecyclerView
*/
private var mRecyclerView: RecyclerView? = null
/**
* 是否重新计算
*/
private var needResetCompute = true
/**
* 状态监听
*/
var mStatusChangeLister : ((Boolean)-> Unit) ? = null
init {
//通过自定义属性获取删除按钮的宽度
val array = mContext.obtainStyledAttributes(attrs, R.styleable.LeftDeleteView)
val delWidth = array.getFloat(R.styleable.LeftDeleteView_deleteBtnWidth, 0f)
array.recycle()
mMinTouchDistance = ViewConfiguration.get(mContext).scaledTouchSlop
//将dp转成PX
val var2: Float = mContext.resources.displayMetrics.density
//计算向右可以滑动的距离,单位PX
mRightCanDistance = (delWidth * var2 + 0.5f).toInt()
setBackgroundColor(Color.TRANSPARENT)
orientation = HORIZONTAL
}
/**
* 设置RecyclerView
*/
fun setRecyclerView(recyclerView: RecyclerView) {
mRecyclerView = recyclerView
}
/**
* 处理触摸事件
*/
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val actionMasked = ev.actionMasked
when (actionMasked) {
MotionEvent.ACTION_DOWN -> {
mInitX = ev.rawX + scrollX
mInitY = ev.rawY
clearAnim()
}
MotionEvent.ACTION_MOVE -> {
if (mInitX - ev.rawX < 0) {
// mRecyclerView拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
return false
}
// y轴方向上达到滑动最小距离, x 轴未达到
if (abs(ev.rawY - mInitY) >= mMinTouchDistance
&& abs(ev.rawY - mInitY) > abs(mInitX - ev.rawX - scrollX)) {
// mRecyclerView拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
return false
}
// x轴方向达到了最小滑动距离,y轴未达到
if (abs(mInitX - ev.rawX - scrollX) >= mMinTouchDistance
&& abs(ev.rawY - mInitY) <= abs(mInitX - ev.rawX - scrollX)) {
// mRecyclerView拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
return true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
mRecyclerView?.let {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = true
}
}
else -> {
}
}
return super.onInterceptTouchEvent(ev)
}
/**
* 处理触摸事件
* 需要注意:何时处理左滑,何时不处理
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean {
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mInitX = ev.rawX + scrollX
mInitY = ev.rawY
clearAnim()
}
MotionEvent.ACTION_MOVE -> {
if (mInitX - ev.rawX < 0) {
// 让父级容器拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
}
// y轴方向上达到滑动最小距离, x 轴未达到
if (abs(ev.rawY - mInitY) >= mMinTouchDistance
&& abs(ev.rawY - mInitY) > abs(mInitX - ev.rawX - scrollX)) {
// mRecyclerView拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
}
// x轴方向达到了最小滑动距离,y轴未达到
if (abs(mInitX - ev.rawX - scrollX) >= mMinTouchDistance
&& abs(ev.rawY - mInitY) <= abs(mInitX - ev.rawX - scrollX)) {
// mRecyclerView拦截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
}
//手指移动距离超过最小距离
var translationX = mInitX - ev.rawX
//滑动距离已经大于右边可伸缩的距离后, 重新设置
if (translationX > mRightCanDistance) {
mInitX = ev.rawX + mRightCanDistance
}
//互动距离小于0,那么重新设置初始位置
if (translationX < 0) {
mInitX = ev.rawX
}
translationX = if (translationX > mRightCanDistance) mRightCanDistance.toFloat() else translationX
translationX = if (translationX < 0) 0f else translationX
// 向左滑动
if (translationX <= mRightCanDistance && translationX >= 0) {
scrollTo(translationX.toInt(), 0)
return true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
mRecyclerView?.let {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = true
}
upFingerAnim()
return true
}
else -> {
}
}
return true
}
/**
* 清理动画
*/
private fun clearAnim() {
mValueAnimator?.let {
it.end()
it.cancel()
mValueAnimator = null
}
}
/**
* 手指抬起开始执行动画
*/
private fun upFingerAnim() {
val scrollX = scrollX
if (scrollX == mRightCanDistance || scrollX == 0) {
//回调显示状态
mStatusChangeLister?.invoke(scrollX == mRightCanDistance)
return
}
clearAnim()
// 如果显出一半松开手指,那么自动完全显示。否则完全隐藏
if (scrollX >= mRightCanDistance / 2) {
mValueAnimator = ValueAnimator.ofInt(scrollX, mRightCanDistance)
//进行滑动
mValueAnimator?.addUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
}
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回调显示的状态
mStatusChangeLister?.invoke(true)
} else {
mValueAnimator = ValueAnimator.ofInt(scrollX, 0)
//进行滑动
mValueAnimator?.addUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
}
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回调关闭的状态
mStatusChangeLister?.invoke(false)
}
}
/**
* 重置按钮删除状态
*/
fun resetDeleteStatus() {
val scrollX = scrollX
if (scrollX == 0) {
return
}
clearAnim()
mValueAnimator = ValueAnimator.ofInt(scrollX, 0)
//进行滑动
mValueAnimator?.addUpdateListener(AnimatorUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
})
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回调关闭的状态
mStatusChangeLister?.invoke(false)
}
}
自定义属性【attrs.xml】
<?xml version="1.0" encoding="utf-8"?>
<resources>
//自定义删除按钮的宽度
<declare-styleable name="LeftDeleteView">
<attr name="deleteBtnWidth" format="float"/>
</declare-styleable>
</resources>
3.左滑删除控件的用法
布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.view.LeftDeleteView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
app:deleteBtnWidth="80"
android:layout_height="80dp">
<!-- 内容的View-->
<include layout="@layout/layout_content"/>
<!--删除按钮的View-->
<include layout="@layout/layout_delete"/>
</com.example.myapplication.view.LeftDeleteView>
RecyclerView.Adapter的代码
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.view.LeftDeleteView
/**
* Description: 适配器
*
* Time:2021/7/1-21:20
* Author:我叫PlusPlus
*/
class MainAdapter(context: Context, list: ArrayList<String>, recyclerView: RecyclerView) : RecyclerView.Adapter<MainAdapter.MainViewHolder>() {
private val mContext = context
private val mData = list
private var leftDeleteView: LeftDeleteView? = null
private val mRecyclerView = recyclerView
private var isShowDeleteView = false
private var mSelectedPoint = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
val view: View = LayoutInflater.from(mContext).inflate(R.layout.item_main_adapter, parent, false)
val deleteView = view as LeftDeleteView
deleteView.setRecyclerView(mRecyclerView)
return MainViewHolder(deleteView)
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val data = mData[position]
(holder.itemView as LeftDeleteView).mStatusChangeLister = {
isShowDeleteView = it
if (it) {
// 如果编辑菜单在显示
leftDeleteView = holder.itemView
mSelectedPoint = position
} else {
mSelectedPoint = -1
}
}
holder.contentText.text = data
holder.contentText.setOnTouchListener { v, event ->
var needIntercept: Boolean = false
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//判断是否有item的删除按钮显示
if (isShowDeleteView) {
//显示的情况下,不管触摸哪个item都会重新状态,并且拦截触摸事件
leftDeleteView?.resetDeleteStatus()
needIntercept = true
}
}
else -> {
}
}
needIntercept
}
holder.contentText.setOnClickListener {
if (isShowDeleteView) {
leftDeleteView?.resetDeleteStatus()
} else {
Toast.makeText(mContext, "内容被点了", Toast.LENGTH_SHORT).show()
}
}
holder.deleteBtn.setOnClickListener {
//重置状态
leftDeleteView?.resetDeleteStatus()
Toast.makeText(mContext, "删除被点了", Toast.LENGTH_SHORT).show()
}
}
override fun getItemCount(): Int {
return mData.size
}
inner class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val contentText: TextView = itemView.findViewById(R.id.content)
val deleteBtn: TextView = itemView.findViewById(R.id.delete_btn)
}
/**
* 重置item状态
* @param point
*/
fun restoreItemView() {
leftDeleteView?.let {
if (isShowDeleteView)
it.resetDeleteStatus()
}
}
}
4.注意点
a.需要将 删除按钮的宽度 和自定义属性中获取删除的宽度保持一直,单位dp
b.删除按钮的重置方法,需要自己在用到的地方添加。
c.(holder.itemView as LeftDeleteView).mStatusChangeLister此为监听删除按钮的状态,返回值boolean, true 显示,false 消失