您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

Android触摸事件部分细节

时间:02-05来源:作者:点击数:

今天,用到了一个可在水平方向滑动ListView的item的控件,是Github上的开源控件,但是用起来总感觉不太好用,水平滑时有时滑不动,因为ListView本身是有上下滑动功能的,当我们的手在滑动的时候,水平滑动的同时也有垂直滑动,如果垂直滑动的距离大于水平滑动的距离,则ListView会把触摸事件拦截掉,它这样做是为了保证ListView可以上下滑动,可是一开始我们本来是想水平滑动item的,这时就会感觉不爽了,所以我要解决的就是,如果一开是水平滑动的,则后面就算上下滑也不要把触摸事件给ListView了,也就是请求父View不要拦截触摸事件。

这下其实也不难,以前学触摸事件时也知道怎么解决的,但是时间久了也会忘,所以这里记录一下,方便以后复习,核心代码如下:

	private var downX = 0f
    private var downY = 0f
    private var hasMove = false

    override fun onTouchEvent(event: MotionEvent): Boolean {
        processEventConflict(event)
        // TODO 做具体的滑动控件的工作
        return super.onTouchEvent(event)
    }

	/** 处理事件突然 */
    private fun processEventConflict(event: MotionEvent) {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downX = event.x
                downX = event.y
                hasMove = false
                isPressed = true // 系统属性,修改按下状态
            }
            MotionEvent.ACTION_MOVE -> {
                hasMove = true
                if (downX == 0f && downY == 0f) {
                    downX = event.x
                    downX = event.y
                    hasMove = false
                }

                // 获取滑动的最小距离,因为如果小于这个距离可能是误操作
                val touchSlop = ViewConfiguration.get(context).scaledTouchSlop

                // 计算水平和垂直的移动距离。因为移动距离有负数的,所以要用绝对值
                val horizontalMove = abs(event.x - downX)
                val verticalMove = abs(event.y - downY)

                // 如果用户处于滑动状态了,并且水平滑动的距离比垂直的大,则请求父View不要拦截touch事件
                if ((horizontalMove >= touchSlop || verticalMove >= touchSlop)
                        && horizontalMove > verticalMove) {
                    parent.requestDisallowInterceptTouchEvent(true)
                }
            }
            MotionEvent.ACTION_UP -> {
                isPressed = false // 系统属性,修改按下状态
                if (!hasMove) {
                    performClick()
                }
                hasMove = false

                /* 因为有时候收不到ACTION_DOWN事件,但是能收到移动事件,所以这里
                   在抬起时清空一下数据,方便在移动时判断是否有收到ActionDown事件 */
                downX = 0f
                downY = 0f
            }
        }
    }

可以看到,在onTouchEvent方法里面,我们调用了一个processEventConflict方法来解决事件突然,后面具体的实现滑动控件的代码就没有写了。有几个关键点需要提一下:

  1. ViewConfiguration.get(context).scaledTouchSlop

    这是Android本身的方法,用于获取代表滑动的最小距离,这个值可能不同的手机它的取值不一样,像我的一个测试手机运行结果是7像素,也就是说如果你的手指滑动距离不够7像素,则应该认为用户还没处于滑动状态,你不能就开始判断用户是向左还是向右滑了。比如用户是想水平方向滑动的,但是一开始操作不好,最开始滑动的几个像素是垂直的,如果这时你就认为是垂直的,而按垂直处理那就有问题了,因为用户有可能接下来手指会慢慢开始往水平方向走。所以在我的需求中,当用户滑动的距离大于这个最小距离时,我再去比较水平和垂直的滑动距离,如果水平距离大,则我认为用户是希望水平滑动的,则我请求父View不要拦截到事件,这样做为父View的ListView的垂直滑动就不会进行了,即使我们后面进行上下滑动时ListView也不会动了。

  2. 移动的距离在计算的时候,是有可能产生负数的,比如从左往右滑,或从右往左滑,距离应该取绝对值。

  3. 单击

    我们写完代码时,会发现onTouchEvent方法上有个警告,如下:

    “Custom view MyGroupView overrides onTouchEvent but not performClick”

    翻译过来就是“MyGroupView这个自定义View覆盖了onTouchEvent但是没有覆盖performClick",这提醒了我,我们在覆盖onTouchEvent方法后,其实这个自定义View的单击事件就没有了,所以我后来做了处理,当收到ACTION_UP事件时,判断一下用户是否有移动操作,如果没有,则是单击操作,这样的话,我们的自定义View除了可以响应滑动的事件外,也可以响应单击事件。除了单击还有长按事件,这个并不常用,如果需要实现的话可以参考一些Android原生View的实现,如查看View的onTouchEvent方法中是如何实现长按的检测的。

    这个"Custom view MyGroupView overrides onTouchEvent but not performClick"警告有点看不懂为什么我们覆盖onTouchEvent的时候就要同时覆盖performClick,百度上说的也不明不白,在国外有一篇文章:https://stackoverflow.com/questions/27462468/custom-view-overrides-ontouchevent-but-not-performclick,这篇文章说的多一点,但是也不是很明白,应该如何覆盖这个performClick,覆盖后里面写什么代码呢没有说,大概了解到这是因为在需要有辅助功能时需要做的,如果我们不想覆盖performClick,则在onTouchEvent上加入一个注解即可,如下:

     	@SuppressLint("ClickableViewAccessibility")
         override fun onTouchEvent(event: MotionEvent): Boolean {
             processEventConflict(event)
             // TODO 做具体的滑动控件的工作
             return super.onTouchEvent(event)
         }
    

    看注解的字符串“ClickableViewAccessibility”也能看出就是因为辅助功能才有的这个警告,什么是“辅助功能”?在设置里面,可以打开辅助功能的,比如一些盲人看不见,它会用手去摸屏幕,会发出语音,这些就是辅助功能,所以大多数情况下我们是不关注这个点的,所以直接加个注解忽略来去除此警告即可。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门