Android自定义view实现列表内左滑删除Item

目录
  • 前言
  • 需求
  • 运行效果
  • 编写代码
  • 主要问题
    • 动态生成TextView
    • 将TextView对齐到当前容器右端
    • 滑动出界问题
    • 滑动开始判定
  • 后续订正

前言

上一篇文章自定义了一个左滑删除的RecyclerView,把view事件分发三个函数dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent实际运用了一下,一些原理通过出现的bug还是挺能加深印象,并且后面还在优化上用上了TouchSlop、VelocityTracker以及GestureDetector,但是真不配那个一个控件搞定安卓自定义view,所以我把上篇博客标题改了,并且希望在接下来的时间里,通过几个自定义view较全面的去学习自定义view的相关知识,话不多说,下面开始1

需求

上篇文章通过RecyclerView去实现了一个左滑的效果,后面突发奇想,既然能通过列表去实现item的左滑,那能不能通过item自己去实现左滑呢?这样我们把item内容写在自定义的layout里面就可以实现左滑了,听起来挺方便,于是就动手做了,少说多做总还是好的。

有了第一篇的内容,item的左滑还是简单多了,主要就是让item跟随滑动,右边自动添加一个删除按钮就够了吧,开始我是这么想的,并总结了三点核心思想:

  • 一个容器,左右两部分,左边外部导入,右边删除框自动增加
  • 在 View 右边追加一个删除框 ,需要在 View 内拦截事件,根据 x 轴滑动距离滑动
  • 在 ConstraintLayout 内部添加一个删除框,左边对其 parent 右边

这里取巧了一下,继承的 ConstraintLayout,这样让添加的删除框对齐 ConstraintLayout的右边就行了。

运行效果

编写代码

代码不多,就直接上代码了,注释写的很详细,后面再提下出现的主要问题:

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.Scroller
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import kotlin.math.abs
/**
 * 左划删除控件
 * 能在控件实现左滑吗?如何传入自定义的布局?
 * 思路:
 * 1、一个容器,左右两部分,左边外部导入,右边删除框 x 增加层级
 * 2、在 View 右边追加一个删除款 x 需要在 View 内拦截事件
 * 3、在 ConstraintLayout 内部添加一个删除框,左边对其 parent 右边
 *
 * @author silence
 * @date 2022-09-27
 */
class LeftDeleteItemLayout : ConstraintLayout {
    private val mDeleteView: View?
    var mDeleteClickListener: OnClickListener? = null
    //流畅滑动
    private var mScroller = Scroller(context)
    //上次事件的横坐标
    private var mLastX = -1f
    //控制控件结束的runnable
    private val stopMoveRunnable: Runnable = Runnable { stopMove() }
    constructor(context: Context) : this(context, null, 0)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr)
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
            super(context, attrs, defStyleAttr, defStyleRes)
    init {
        //kotlin的初始化函数
        mDeleteView = makeDeleteView(context)
        addView(mDeleteView)
    }
    //创建删除框,设置好位置对齐自身最右边
    private fun makeDeleteView(context: Context): View {
        val deleteView = TextView(context)
        //给当前控件一个id,用于删除控件约束
        this.id = generateViewId()
        //设置布局参数
        deleteView.layoutParams = LayoutParams(
            dp2px(context, 100f), 0
        ).apply {
            //设置约束条件
            leftToRight = id
            topToTop = id
            bottomToBottom = id
        }
        //设置其他参数
        deleteView.text = "删除"
        deleteView.gravity = Gravity.CENTER
        deleteView.setTextColor(Color.WHITE)
        deleteView.textSize = sp2px(context,18f).toFloat()
        deleteView.setBackgroundColor(Color.RED)
        //设置点击回调
        deleteView.setOnClickListener(mDeleteClickListener)
        return deleteView
    }
    //拦截事件
    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            when(event.action) {
                //down事件记录x,不拦截,当move的时候才会用到
                MotionEvent.ACTION_DOWN -> mLastX = event.x
                //拦截本控件内的移动事件
                MotionEvent.ACTION_MOVE -> return true
            }
        }
        return super.onInterceptTouchEvent(event)
    }
    //处理事件
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            when(event.action) {
                MotionEvent.ACTION_MOVE -> moveItem(event)
                MotionEvent.ACTION_UP -> stopMove()
            }
        }
        return super.onTouchEvent(event)
    }
    private fun moveItem(e: MotionEvent) {
        //Log.e("TAG", "moveItem: mLastX=$mLastX")
        //如果没有收到down事件,不应该移动
        if (mLastX == -1f) return
        val dx = mLastX - e.x
        //更新点击的横坐标
        mLastX = e.x
        //检查mItem移动后应该在[-deleteLength, 0]内
        val deleteWidth = mDeleteView!!.width
        if ((scrollX + dx) <= deleteWidth && (scrollX + dx) >= 0) {
            //触发移动
            scrollBy(dx.toInt(), 0)
        }
        //如果一段时间没有移动时间,mLastX还没被stopMove重置为-1,那就是移动到其他地方了
        //设置200毫秒没有新事件就触发stopMove
        removeCallbacks(stopMoveRunnable)
        postDelayed(stopMoveRunnable, 200)
    }
    private fun stopMove() {
        //如果移动过半了,应该判定左滑成功
        val deleteWidth = mDeleteView!!.width
        if (abs(scrollX) >= deleteWidth / 2f) {
            //触发移动至完全展开
            mScroller.startScroll(scrollX, 0, deleteWidth - scrollX, 0)
        }else {
            //如果移动没过半应该恢复状态,则恢复到原来状态
            mScroller.startScroll(scrollX, 0, - scrollX, 0)
        }
        invalidate()
        //清除状态
        mLastX = -1f
    }
    //流畅地滑动
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
    //单位转换
    @Suppress("SameParameterValue")
    private fun dp2px(context: Context, dpVal: Float): Int {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, dpVal, context.resources
                .displayMetrics
        ).toInt()
    }
    @Suppress("SameParameterValue")
    private fun sp2px(context: Context, spVal: Float): Int {
        val fontScale = context.resources.displayMetrics.scaledDensity
        return (spVal * fontScale + 0.5f).toInt()
    }

主要问题

动态生成TextView

这个主要就是通过代码生成一个TextView,不是很难,提一下。

将TextView对齐到当前容器右端

这里利用ConstraintLayout取巧做的还是不错的,因为如果要自己去实现一个在屏幕外的对齐,至少要在onMeasure中获得宽度,再去onLayout里面摆放到右侧屏幕外。

这里也有一些问题,首先是设置动态生成的TextView参数,然后是设置ConstraintLayout内的约束条件,因为约束标记必须要用到id,还得为当前控件生成一个id,最后就是做一个回调接口了。

滑动出界问题

还有一个没有预料到的问题是当滑动超过当前view的范围时,ACTION_MOVE和ACTION_UP都无法接收到,这就没法知道移动是否结束了。这里因为我们的自定义view是一个viewgroup,所以没法消耗ACTION_DOWN事件,所以后续的事件序列并不会交到当前的item上,这就麻烦了,所以这个需求本质上就是不合理的,但是还是要解决问题吧!

这里我通过View类的postDelayed,延迟运行一个runnable去停止滑动,当每次滑动的时候又去停止这个runnable。整个逻辑运行起来就是,滑动没有出界,移动的时候先移除延迟的停止逻辑,再发送延迟的停止逻辑,直到ACTION_UP触发停止,若滑动出界了,没有去移除延迟的停止逻辑,就会在一端时间后自动触发停止。

有点绕,但是还是挺简单的,里面的原理也简单讲一下。实际上View的postDelayed会通过主线程的handler去延迟执行,如果有了解handler机制,可以知道handler并不仅仅可以发送message,同样也可以发送runnable,类似移除message,同样也可以移除runnable。

滑动开始判定

另一个预料之外的问题是当滑动从其他item移动到当前item的时候,即使没有收到ACTION_DOWN事件,也会触发滑动,这个很不符合逻辑。我这就在stopMove里面将mLastX改为了-1,初始值也是-1,如果在moveItem中值是-1,就说明没有被ACTION_DOWN事件设定mLastX,即按下的时候并不在当前item,应当舍弃滑动。

后续订正

onTouchEvent有误

    //处理事件
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.action) {
            MotionEvent.ACTION_DOWN -> return true
            MotionEvent.ACTION_MOVE -> moveItem(event)
            MotionEvent.ACTION_UP -> stopMove()
        }
        return super.onTouchEvent(event)
    }

增加对ACTION_DOWN的拦截,因为如果ACTION_DOWN没在view处有被处理的话,会被丢弃,如果被view拦截了的话,move事件又不会经过onInterceptTouchEvent函数。真不知道当时写的时候是怎么运行通过的。。。

到此这篇关于Android自定义view实现列表内左滑删除Item的文章就介绍到这了,更多相关Android左滑删除Item内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android使用Item Swipemenulistview实现仿QQ侧滑删除功能

    大家都用过QQ,肯定有人好奇QQ滑动删除Item的效果是怎样实现的,其实我们使用Swipemenulistview就可以简单的实现.先看看我们项目中的效果: 使用的时候可以把Swipemenulistview作为一个library,也可以把Swipemenulistview的源码拷贝到我们的项目中来,使用步骤大致可以分为三步:1.在布局中配置:2.在Java代码中初始化配置:3.按钮点击事件的处理  1.在布局中配置 xml布局文件中只需要简单使用这个自定义的ListView就行了,需要注意的是

  • Android使用ListView批量删除item的方法

    本文实例讲述了Android使用ListView批量删除item的方法.分享给大家供大家参考,具体如下: 利用CheckBox选中一个或多个item,最后批量删除它们. 程序运行效果图如下:   下面开始上码: (代码已更正,已解决滚动时紊乱的问题) package com.test.adapter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;

  • Android item长按删除功能

    这个可以在Adapter里面写 一般写完之后都会调用 public void removeItem(int pos){ this.mDatas.remove(pos); notifyItemRemoved(pos); } 可是这样写的话,删除多次,会乱套,没有刷新 所以改这样子写 public void removeItem(int pos){ this.mDatas.remove(pos); notifyItemRemoved(pos); if(pos != mDatas.size()){ /

  • Android自定义view实现列表内左滑删除Item

    目录 前言 需求 运行效果 编写代码 主要问题 动态生成TextView 将TextView对齐到当前容器右端 滑动出界问题 滑动开始判定 后续订正 前言 上一篇文章自定义了一个左滑删除的RecyclerView,把view事件分发三个函数dispatchTouchEvent.onInterceptTouchEvent.onTouchEvent实际运用了一下,一些原理通过出现的bug还是挺能加深印象,并且后面还在优化上用上了TouchSlop.VelocityTracker以及GestureDe

  • Android自定义组合控件之自定义下拉刷新和左滑删除实例代码

    绪论 最近项目里面用到了下拉刷新和左滑删除,网上找了找并没有可以用的,有比较好的左滑删除,但是并没有和下拉刷新上拉加载结合到一起,要不就是一些比较水的结合,并不能在项目里面使用,小编一着急自己组合了一个,做完了和QQ的对比了一下,并没有太大区别,今天分享给大家,其实并不难,但是不知道为什么网上没有比较好的Demo,当你的项目真的很急的时候,又没有比较好的Demo,那么"那条友谊的小船儿真是说翻就翻啊",好了,下面先来具体看一下实现后的效果吧: 代码已经上传到Github上了,小伙伴们记

  • Android ListView实现仿iPhone实现左滑删除按钮的简单实例

    需要自定义ListView.这里就交FloatDelListView吧. 复写onTouchEvent方法.如下: @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN:<BR> // 获取按下的条目视图(child view) int childCount = getChildCount(); int[] listViewCo

  • 微信小程序实现列表项左滑删除效果

    本文实例为大家分享了微信小程序实现列表项左滑删除效果的具体代码,供大家参考,具体内容如下 效果 图片 WXML <view class="container"> <!-- 收货地址 --> <view class="address"> <view class="left"> <view class="icon"> <image src="../../i

  • Android实现左滑删除列表功能

    本文实例为大家分享了Android自定义左滑删除列表的具体代码,供大家参考,具体内容如下 1.布局文件view_left_slide_remove.xml 包含一个RelativeLayout和TextView,RelativeLayout是实际界面,TextView是删除按钮. <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://sch

  • Android使用PullToRefresh完成ListView下拉刷新和左滑删除功能

    ListView下刷新刷功能相信从事Android开发的猿友们并不陌生,包括现在Google亲儿子SwipeRefreshLayout实现效果在一些APP上也能看见(不过个人不喜欢官方的刷新效果).本文就带领一些刚入门android的朋友或者一起爱分享的朋友来简单的实现ListView的下拉刷新和左滑删除效果. 一.本文主要内容: 使用PullToRefresh完成ListView下拉.上拉刷新: 扩展PullToRefresh完美的实现ListView左滑删除效果: 注意:本文中的PullTo

  • Android实现左滑删除控件

    背景:在android开发中,列表是经常会使用到的一个主要控件,列表中可以展示大量的数据,像订单.商品.通讯录.浏览记录或者关注列表等等.可能产品一开始需求只做简单的数据展示,但后期随着功能越来越多,越来越完善,产品可能说在列表里面增加一些交互能力.比如说订单列表里面,一开始只是展示订单数据,后面需要加上删除订单的功能,以前Android中这种功能要的很多的可能就是长按操作这种的,因为程序猿只需要很少的代码就能实现.但是ios的习惯操作是左滑删除,为了保持统一的操作习惯,两端保持一致,最终产品会

  • Android Recyclerview实现左滑删除功能

    本文实例为大家分享了Android Recyclerview实现左滑删除的具体代码,供大家参考,具体内容如下 1.先创建一个工具类 SlideRecyclerView public class SlideRecyclerView extends RecyclerView {       private static final String TAG = "SlideRecyclerView";     private static final int INVALID_POSITION

  • Android自定义view实现侧滑栏详解

    目录 前言 需求 效果图 编写代码 主要问题 前言 上一篇文章学了下自定义View的onDraw函数及自定义属性,做出来的滚动选择控件还算不错,就是逻辑复杂了一些.这篇文章打算利用自定义view的知识,直接手撕一个安卓侧滑栏,涉及到自定义LayoutParams.带padding和margin的measure和layout.利用requestLayout实现动画效果等,有一定难度,但能重新学到很多知识! 需求 这里类似旧版QQ(我特别喜欢之前的侧滑栏),有两层页面,滑动不是最左侧才触发的,而是从

  • Android自定义View实现仿GitHub的提交活跃表格

    说明 本文可能需要一些基础知识点,如Canvas,Paint,Path,Rect等类的基本使用,建议不熟悉的同学可以学习GcsSloop安卓自定义View教程目录,会帮助很大. 上图就是github的提交表格,直观来看可以分为几个部分进行绘制: (1)各个月份的小方格子,并且色彩根据提交次数变化,由浅到深 (2)右下边的颜色标志,我们右对齐就可以了 (3)左边的星期,原图是从周日画到周六,我们从周一画到周日 (4)上面的月份,我们只画出1-12月 (5)点击时候弹出当天的提交情况,由一个小三角和

随机推荐