小米 Android ACTION_UP不响应

问题概要

在小米手机(测试机为小米4LTE)上,对一个TextView/Button设置OnTouchListener,长按View抬起时,并没有收到ACTION_UP时间,而是收到了ACTION_CANCEL事件。

理论

查阅资料,发现如下理论:当控件收到前驱事件(什么叫前驱事件?一个从DOWN一直到UP的所有事件组合称为完整的手势,中间的任意一次事件对于下一个事件而言就是它的前驱事件)之后,后面的事件如果被父控件拦截,那么当前控件就会收到一个CANCEL事件,并且把这个事件会传递给它的子事件。(注意:这里如果在控件的onInterceptTouchEvent中拦截掉CANCEL事件是无效的,它仍然会把这个事件传给它的子控件)之后这个手势所有的事件将全部拦截,也就是说这个事件对于当前控件和它的子控件而言已经结束了。按照该理论,MUI在系统级将长按View的最后一个ACTION_UP事件拦截并消费掉,并发出ACTION_CANCEL事件,并将这个事件传递给设置了OnTouchListener的View上。

实践Demo

我们先不讨论项目中遇到的问题,先按照上述理论做了个Demo

如下:

private void testTouchMUI() {
        tvTouch.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i("zxg","event x:"+event.getX()+",event y:"+event.getY());
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        Log.i("zxg", "action down");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.i("zxg", "action move");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.i("zxg", "action up");
                        break;
                    case MotionEvent.ACTION_CANCEL:
                        Log.i("zxg", "action cancel");
                        break;
                }
                return true;
            }
        });
    }

小米4 LTE 测试结果

点击快速抬起

10-23 09:35:45.468 11218-11218/com.saic.saic_ui I/zxg: event x:234.0,event y:98.0
    action down
10-23 09:35:45.543 11218-11218/com.saic.saic_ui I/zxg: event x:234.0,event y:98.0
    action up

可以看到收到UP事件,是一次完整的点击事件

按下后滑动并抬起

10-23 09:37:31.083 11218-11218/com.saic.saic_ui I/zxg: event x:423.0,event y:191.0
    action down
10-23 09:37:31.111 11218-11218/com.saic.saic_ui I/zxg: event x:423.0,event y:191.0
    action move
...
10-23 09:37:31.447 11218-11218/com.saic.saic_ui I/zxg: event x:285.66003,event y:178.0
    action move
10-23 09:37:31.461 11218-11218/com.saic.saic_ui I/zxg: event x:285.66003,event y:178.0
    action up

可以看到收到多次move事件后最终也收到了UP事件

长按抬起

10-23 09:39:30.423 11218-11218/com.saic.saic_ui I/zxg: event x:232.0,event y:148.0
    action down
10-23 09:39:30.592 11218-11218/com.saic.saic_ui I/zxg: event x:232.0,event y:148.0
    action move
10-23 09:39:31.244 11218-11218/com.saic.saic_ui I/zxg: event x:322.0,event y:1296.0
    action cancel

奇怪的事件发生了,最终并没有收到UP事件,而是CANCEL事件

我们先来看下其他机型情况,再分析小米手机长按的log

三星S10测试结果

点击快速抬起

与小米4log一致,不赘述

按下后滑动并抬起

与小米4log一致,不赘述

长按抬起

10-23 09:46:06.722 31076-31076/com.saic.saic_ui I/zxg: event x:625.24023,event y:160.44531
    action down
10-23 09:46:06.747 31076-31076/com.saic.saic_ui I/zxg: event x:624.22046,event y:161.00195
    action move
10-23 09:46:06.763 31076-31076/com.saic.saic_ui I/zxg: event x:623.9219,event y:161.00195
    action move
10-23 09:46:06.780 31076-31076/com.saic.saic_ui I/zxg: event x:623.13086,event y:161.5586
    action move
10-23 09:46:06.813 31076-31076/com.saic.saic_ui I/zxg: event x:622.6035,event y:161.5586
    action move
10-23 09:46:06.913 31076-31076/com.saic.saic_ui I/zxg: event x:622.33984,event y:162.67188
    action move
10-23 09:46:06.930 31076-31076/com.saic.saic_ui I/zxg: event x:622.8672,event y:162.67188
    action move
10-23 09:46:07.412 31076-31076/com.saic.saic_ui I/zxg: event x:622.8672,event y:162.67188
    action up

可以看到长按后抬起最终收到了ACTION_UP事件。下面我们分析下这两份长按日志如下区别:

  1. 小米日志event事件的坐标值只保留了小数点后1位且在这个过程中未变化过。而三星手机精确度比较高保留到了小数点后4位。换句话说三星手机较为灵敏,可以更细微感知手指滑动。

  2. 小米手机最终的CANCEL事件event坐标值突然增大,与之前的move事件DOWM及MOVE事件差距较大,推测:DOWN及MOVE事件坐标是以为左上角为(0,0)点,而最终CANCEL事件坐标是以屏幕左上角作为(0,0)点。这种推测也佐证了是MUI系统级拦截UP事件,并发出CANCEL事件。

  3. 使用三星手机很难做到长按抬起,事件坐标点一直不变化,因为其灵敏度很高,所以假如三星手机长按抬起坐标点不变化是否也是收到CANCEL事件,我们就不得而知了。

结论:
对于小米手机,无论是灵敏度低导致或系统拦截UP事件导致,最终的结果是长按不起收不到UP事件,那么针对小米手机,就要在CANCEL事件中执行与UP事件一样的处理。

具体问题

自研IM长按录音抬起发送语音的功能,在小米4中,由于长按抬起,一直走CANCEL事件,导致一直取消录音,没有走到UP事件中发送语音的逻辑。针对该问题,我们做如下修改:

 event?.action == MotionEvent.ACTION_CANCEL -> {
                        Log.i("zxg", "cancel ACTION_CANCEL")
                        var location = IntArray(2)
                        btn_speak.getLocationOnScreen(location)
                        if (RomUtils.isMiui()) {
                            //如果是小米手机UP一瞬间系统给到的event的坐标体系并不是以btn_speak左上角为(0,0)的坐标值
                            //而是以整个屏幕左上角为(0,0)的坐标值,原因:猜测MUI系统层面拦截UP事件,btn_speak作为子控件,会收到CANCEL事件
                            //参考:https://blog.csdn.net/bornonew/article/details/90897001
                            //参考:https://blog.csdn.net/qq_23934247/article/details/88711079
                            //所以此时我们要获取btn_speak相对整个屏幕的绝对坐标值并判断event事件的左边是否在控件内执行UP事件中的发送录音逻辑
                            Log.info(TAG,"speak view start x:"+location[0]+",speak view start y:"+location[1])
                            IMLog.info(TAG, "boundary x:" + (location[0] + btn_speak.width) + ",boundary y:" + (location[1] + btn_speak.height))
                            Log.info(TAG,"event x:"+event?.x+",event y"+event?.y)
                            if ((event?.x > location[0] && event?.x < location[0] + btn_speak.width) && (event?.y > location[1] && event?.y < location[1] + btn_speak.height)) {
                                IMLog.info(TAG, "mui adapte need recrod")
                               //录音逻辑
                            } else {
                               //取消录音逻辑
                            }
                        } else {
                            //取消录音逻辑
                        }
                    }

可以看到,我们并没有直接执行录音逻辑,而是要判断event事件坐标点是否落在录音按钮内,因为需要顾及其他功能逻辑,例如上滑取消录音的逻辑

由于最终的CANCEL event事件坐标点是以整个屏幕左上角为(0,0)点,所以我们需要通过getLocationOnScreen()获取录音按钮左上角的绝对坐标(以屏幕左上角为(0,0)点的坐标),并且根据按钮的宽高计算出录音按钮的坐标范围,以此作为标准判断event事件坐标点是否落在按钮内

目前该改动已通过小米4/小米4 note/小米9测试,其他品牌尝试了三星、vivo。还需要大量兼容性测试,重点测试小米其他型号手机

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页