日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

千里马 android framework之MotionEvent.ACTION_CANCEL怎么产生-讨厌的android触摸面试题

發(fā)布時間:2023/12/20 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 千里马 android framework之MotionEvent.ACTION_CANCEL怎么产生-讨厌的android触摸面试题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

hi,粉絲朋友!
大家對于MotionEvent.ACTION_CANCEL這個cancel事件是不是感覺又熟悉又陌生,熟悉是因為經(jīng)常在onTouch識別觸摸事件時候會把它和ACTION_UP放在一塊處理,基本停留在字面意思理解為 “”取消“”
新課程優(yōu)惠獲取請加入qq群:422901085
Android手機大廠Framework系統(tǒng)-Input系統(tǒng)專題實戰(zhàn)課

[入門課,實戰(zhàn)課,跨進程專題
ps需要學(xué)習(xí)深入framework課程和課程優(yōu)惠

ACTION_CANCEL觸發(fā)場景和原因:

customTextView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {Log.i("test2"," onTouch motionEvent = " + motionEvent);if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL || motionEvent.getAction() == MotionEvent.ACTION_UP ) {Log.i("test2"," onTouch ACTION_CANCEL = " ,new Exception());}return true;} });

所以這里本節(jié)課就來帶大家深入理解這里ACTION_CANCEL

首先我們來分析應(yīng)用觸摸最常見的ACTION_CANCEL情況:

這個就是我們常見的一個父布局MyLayout,它裝載了2個子控件textview1和textview2.假設(shè)一個觸摸事件到來,我們知道觸摸事件的傳遞順序就是:父布局 – 》 子布局
這樣的一個順序,也就是其實事件父布局是具有完全的決策權(quán)利來是否給子布局,可以通過方法onInterceptTouchEvent。
接下來我們又來看看MotionEvent,一個正常完整觸摸應(yīng)該是怎么個順序呢?
ACTION_DOWN – > ACTION_MOVE --> ACTION_UP
所以說我們控件正常事件處理就是最少要有DOWN --》UP兩個事件,才代表事件介紹,那么你會問如果只有DOWN,和MOVE是否可以呢?答案:當(dāng)然是不可以的
為啥呢?
大家可以想一下這樣一個場景,你是一個按鈕,來了觸摸事件DOWN了后,你把按鈕變成selected狀態(tài)了,你本來一直等值來個UP事件來變回正常狀態(tài),如果系統(tǒng)發(fā)生依次你沒有收到UP會怎么樣呢?那就是你的應(yīng)用按鈕就永遠處于選中狀態(tài)無法取消,一直到進程關(guān)閉(當(dāng)然說是正常你沒有特別處理情況)。

所以有了以上基礎(chǔ)后,大家就知道有了DOWN事件一般都要有UP事件才算完整,但是有一些場景他就是可能收到了DOWN,后面收不到UP了怎么辦?
比如看如下例子:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i("test2","onInterceptTouchEvent ev = " + ev);if (ev.getAction() == MotionEvent.ACTION_MOVE ) {//我們不攔截DOWN事件,但是攔截MOVE事件,父布局一攔截,子控件就會收到ACTION_CANCELreturn true;}return super.onInterceptTouchEvent(ev);}

這種情況我們父布局只并沒有攔截DOWN事件,所以DOWN傳遞給你textview1,但是因為在MOVE時候我們父布局攔截了,即說明事件不再傳遞給子控件了textview1,那么這個是不是就是textview1收到了DOWN,但是收不到UP情況,不完整了,那該怎么辦?

這個時候其實就是今天重點介紹的ACTION_CANCEL出廠,他就是來幫忙解決上面的因為父布局中間進行了觸摸事件攔截,但是子布局又要一個完整觸摸過程,那么就需要傳遞一個觸摸事件給子控件,那么傳遞什么合適?
大家肯定會想UP最合適,UP不就可以了?但是你要想想用戶還沒有UP啊?如果傳遞了UP是不是也可能會有問題,所以這時候就是傳遞ACTION_CANCEL,它代表是取消,即代表這個觸摸事件到此被取消結(jié)束了。控件收到需要自己做對應(yīng)的掃尾工作,保證事件完整。

上面我們已經(jīng)對ACTION_CANCEL觸發(fā)場景和原因已經(jīng)清楚,接下來看看源碼是怎么處理的:

ACTION_CANCEL出現(xiàn)源碼分析

app代碼:
layout.xml

android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:paddingTop="100dp"><TextViewandroid:id="@+id/custom_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello CustomLayout !"></TextView></com.example.anrdemo.CustomLayout>

代碼端對onTouch的事件進行監(jiān)聽:

TextView customTextView = (TextView)findViewById(R.id.custom_text);customTextView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {Log.i("test2"," onTouch motionEvent = " + motionEvent);if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL || motionEvent.getAction() == MotionEvent.ACTION_UP ) {//變成了CANCEL后打印一下堆棧,追蹤框架哪里進行了改變Log.i("test2"," onTouch ACTION_CANCEL = " ,new Exception());}return true;}}); 2021-11-27 13:50:06.911 7010-7010/com.example.anrdemo I/test2: onTouch ACTION_CANCEL = java.lang.Exceptionat com.example.anrdemo.MainActivity$2.onTouch(MainActivity.java:46)at android.view.View.dispatchTouchEvent(View.java:13949)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3060)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2755)at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:465)at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1849)at android.app.Activity.dispatchTouchEvent(Activity.java:4011)at com.example.anrdemo.MainActivity.dispatchTouchEvent(MainActivity.java:72)at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:423)at android.view.View.dispatchPointerEvent(View.java:14212)at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5652)at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5455)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4958)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5011)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4977)at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5117)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4985)at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5174)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4958)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5011)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4977)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4985)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4958)at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7675)at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7644)at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7605)at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7800)at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:188)at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:178)at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:7751)at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:7824)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)at android.view.Choreographer.doCallbacks(Choreographer.java:791)at android.view.Choreographer.doFrame(Choreographer.java:719)at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)at android.os.Handler.handleCallback(Handler.java:883)at android.os.Handler.dispatchMessage(Handler.java:100)

這里就打印出來了框架調(diào)用到onTouch中MotionEvent變成CANCEL的流程,方便我們進行源碼分析。
具體大家可以自己取追蹤,這里我這邊列出結(jié)果:
base/core/java/android/view/ViewGroup.java

// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}}

這里onInterceptTouchEvent會調(diào)用到我們CustomLayout的onInterceptTouchEvent,我們識別到了如果MOVE就return true,所以intercepted這個時候也是true。

final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits))

這里cancelChild就變成了true,所以

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//根據(jù)前面cancelChild變成了CANCELevent.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;} //---省略部分 }

從以上可以看出,結(jié)合我們Input專題學(xué)習(xí)知識,我們知道觸摸事件InputDispatcher傳遞給App實際還是ACTION_MOVE,但是app進程ViewGroup的策略把事件變成了ACTION_CANCEL來保證事件的完整性。
總結(jié)流程圖:

總結(jié)

以上是生活随笔為你收集整理的千里马 android framework之MotionEvent.ACTION_CANCEL怎么产生-讨厌的android触摸面试题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。