小米 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。還需要大量兼容性測試,重點測試小米其他型號手機

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章