1.引言
左滑刪除item在現(xiàn)在的聊天軟件中隨處可見捶闸,最常見的就是和RecyclerView的Item綁定一起使用。接下來就一起開發(fā)一個(gè)簡(jiǎn)單的左滑刪除自定義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
/**
* 右邊可滑動(dòng)距離
*/
private var mRightCanDistance :Int = 0
/**
* 按下時(shí) x
*/
private var mInitX : Float = 0f
/**
* 按下y
*/
private var mInitY : Float = 0f
/**
* 屬性動(dòng)畫
*/
private var mValueAnimator: ValueAnimator? = null
/**
* 動(dòng)畫時(shí)長(zhǎng)
*/
private var mAnimDuring = 300
/**
* RecyclerView
*/
private var mRecyclerView: RecyclerView? = null
/**
* 是否重新計(jì)算
*/
private var needResetCompute = true
/**
* 狀態(tài)監(jiān)聽
*/
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轉(zhuǎn)成PX
val var2: Float = mContext.resources.displayMetrics.density
//計(jì)算向右可以滑動(dòng)的距離,單位PX
mRightCanDistance = (delWidth * var2 + 0.5f).toInt()
setBackgroundColor(Color.TRANSPARENT)
orientation = HORIZONTAL
}
/**
* 設(shè)置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軸方向上達(dá)到滑動(dòng)最小距離, x 軸未達(dá)到
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軸方向達(dá)到了最小滑動(dòng)距離积担,y軸未達(dá)到
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)
}
/**
* 處理觸摸事件
* 需要注意:何時(shí)處理左滑,何時(shí)不處理
*/
@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) {
// 讓父級(jí)容器攔截
mRecyclerView?.let {
if (needResetCompute) {
it.requestDisallowInterceptTouchEvent(false)
needResetCompute = false
}
}
}
// y軸方向上達(dá)到滑動(dòng)最小距離, x 軸未達(dá)到
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軸方向達(dá)到了最小滑動(dòng)距離猬仁,y軸未達(dá)到
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
}
}
}
//手指移動(dòng)距離超過最小距離
var translationX = mInitX - ev.rawX
//滑動(dòng)距離已經(jīng)大于右邊可伸縮的距離后, 重新設(shè)置
if (translationX > mRightCanDistance) {
mInitX = ev.rawX + mRightCanDistance
}
//互動(dòng)距離小于0帝璧,那么重新設(shè)置初始位置
if (translationX < 0) {
mInitX = ev.rawX
}
translationX = if (translationX > mRightCanDistance) mRightCanDistance.toFloat() else translationX
translationX = if (translationX < 0) 0f else translationX
// 向左滑動(dòng)
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
}
/**
* 清理動(dòng)畫
*/
private fun clearAnim() {
mValueAnimator?.let {
it.end()
it.cancel()
mValueAnimator = null
}
}
/**
* 手指抬起開始執(zhí)行動(dòng)畫
*/
private fun upFingerAnim() {
val scrollX = scrollX
if (scrollX == mRightCanDistance || scrollX == 0) {
//回調(diào)顯示狀態(tài)
mStatusChangeLister?.invoke(scrollX == mRightCanDistance)
return
}
clearAnim()
// 如果顯出一半松開手指,那么自動(dòng)完全顯示湿刽。否則完全隱藏
if (scrollX >= mRightCanDistance / 2) {
mValueAnimator = ValueAnimator.ofInt(scrollX, mRightCanDistance)
//進(jìn)行滑動(dòng)
mValueAnimator?.addUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
}
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回調(diào)顯示的狀態(tài)
mStatusChangeLister?.invoke(true)
} else {
mValueAnimator = ValueAnimator.ofInt(scrollX, 0)
//進(jìn)行滑動(dòng)
mValueAnimator?.addUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
}
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回調(diào)關(guān)閉的狀態(tài)
mStatusChangeLister?.invoke(false)
}
}
/**
* 重置按鈕刪除狀態(tài)
*/
fun resetDeleteStatus() {
val scrollX = scrollX
if (scrollX == 0) {
return
}
clearAnim()
mValueAnimator = ValueAnimator.ofInt(scrollX, 0)
//進(jìn)行滑動(dòng)
mValueAnimator?.addUpdateListener(AnimatorUpdateListener { animation ->
val value = animation.animatedValue as Int
scrollTo(value, 0)
})
mValueAnimator?.duration = mAnimDuring.toLong()
mValueAnimator?.start()
//回調(diào)關(guān)閉的狀態(tài)
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">
<!-- 內(nèi)容的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) {
//顯示的情況下的烁,不管觸摸哪個(gè)item都會(huì)重新狀態(tài),并且攔截觸摸事件
leftDeleteView?.resetDeleteStatus()
needIntercept = true
}
}
else -> {
}
}
needIntercept
}
holder.contentText.setOnClickListener {
if (isShowDeleteView) {
leftDeleteView?.resetDeleteStatus()
} else {
Toast.makeText(mContext, "內(nèi)容被點(diǎn)了", Toast.LENGTH_SHORT).show()
}
}
holder.deleteBtn.setOnClickListener {
//重置狀態(tài)
leftDeleteView?.resetDeleteStatus()
Toast.makeText(mContext, "刪除被點(diǎn)了", 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狀態(tài)
* @param point
*/
fun restoreItemView() {
leftDeleteView?.let {
if (isShowDeleteView)
it.resetDeleteStatus()
}
}
}
4.注意點(diǎn)
a.需要將 刪除按鈕的寬度 和自定義屬性中獲取刪除的寬度保持一直诈闺,單位dp
b.刪除按鈕的重置方法渴庆,需要自己在用到的地方添加。
c.(holder.itemView as LeftDeleteView).mStatusChangeLister此為監(jiān)聽刪除按鈕的狀態(tài)雅镊,返回值boolean, true 顯示襟雷,false 消失