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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android事件分发之ACTION_CANCEL机制及作用

發(fā)布時(shí)間:2023/12/20 Android 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android事件分发之ACTION_CANCEL机制及作用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

  • ACTION_CANCEL產(chǎn)生場(chǎng)景
  • ACTION_CANCEL作用
  • FLAG_DISALLOW_INTERCEPT的作用

如果要查看ACTION_MOVE與ACTION_UP的事件傳遞機(jī)制,查看Android事件分發(fā)之ACTION_MOVE與ACTION_UP的傳遞機(jī)制

ACTION_CANCEL產(chǎn)生場(chǎng)景

在閱讀ViewGroup事件分發(fā)相關(guān)源碼過(guò)程中,有時(shí)候會(huì)見(jiàn)到ACTION_CANCEL這一事件。那么這一事件是如何產(chǎn)生的呢?按照網(wǎng)上的說(shuō)法,當(dāng)手指從當(dāng)前view移出后,當(dāng)前view就會(huì)收到ACTION_CANCEL這一事件,這一定是正確的嗎?下面我們來(lái)看兩個(gè)例子:

例子1:

import android.content.Context import android.support.constraint.ConstraintLayout import android.util.AttributeSet import android.util.Log import android.view.MotionEventclass CustomViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) {var hasInterceptMoveEvent = falseoverride fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent")if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) {hasInterceptMoveEvent = truereturn true}return false}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")return super.dispatchTouchEvent(ev)}}

我們自定義一個(gè)ViewGroup,覆蓋它的onInterceptTouchEvent方法和dispatchTouchEvent方法。在onInterceptTouchEvent方法中我們只攔截了一次MotionEvent.ACTION_MOVE事件。hasInterceptMoveEvent用于控制只攔截一次。而在dispatchTouchEvent方法中,我們打印出當(dāng)前ViewGroup處理的事件。看下getAction方法定義:

fun getAction(action: Int?): String {return when (action) {MotionEvent.ACTION_DOWN -> "MotionEvent.ACTION_DOWN"MotionEvent.ACTION_MOVE -> "MotionEvent.ACTION_MOVE"MotionEvent.ACTION_UP -> "MotionEvent.ACTION_UP"MotionEvent.ACTION_CANCEL -> "MotionEvent.ACTION_CANCEL"else -> "OTHER"} }

其實(shí)就是根據(jù)Action對(duì)應(yīng)的Int值轉(zhuǎn)化為字符串,讓我們的Log更加直觀。

而在ViewGroup內(nèi)部,我們放置了一個(gè)自定義Button。代碼如下:

class CusButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : Button(context, attrs, defStyleAttr) {override fun dispatchTouchEvent(event: MotionEvent?): Boolean {Log.d("TAG", "${getAction(event?.action)}:CusButton dispatchTouchEvent")return true} }

這個(gè)自定義Button只是將當(dāng)前dispatchTouchEvent方法收到的事件打印出來(lái)。


現(xiàn)在我們進(jìn)行如下操作:

手指按住button,然后移動(dòng),移出button外,然后松開(kāi)。我們看下打印出的Log:

04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_CANCEL:CusButton dispatchTouchEvent
04-23 15:13:48.591 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.607 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.624 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.642 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.658 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.674 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.691 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.708 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.724 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.741 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.758 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.775 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.791 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.808 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.825 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.841 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.859 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.866 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.867 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent

我們看到CusButton確實(shí)收到了一個(gè)ACTION_CANCEL事件,并且在這個(gè)事件之后,CusButton并沒(méi)有收到任何事件。所以我們大概能夠猜到:如果ViewGroup攔截了Move事件,那么這個(gè)Move事件將會(huì)轉(zhuǎn)化為Cancel事件傳遞給子view。

例子2:

如果我們的ViewGroup不攔截Move事件,那么是否也會(huì)產(chǎn)生Cancel事件呢?
我們修改一下CustomViewGroup源碼:

class CustomViewGroup @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) {// var hasInterceptMoveEvent = falseoverride fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent") // if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) { // hasInterceptMoveEvent = true // return true // }return false}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")return super.dispatchTouchEvent(ev)}}

即onInterceptTouchEvent方法不攔截事件。同樣的操作我們?cè)賮?lái)看下Log:

04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:29:24.104 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.105 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.154 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.155 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.275 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CusButton dispatchTouchEvent

我們可以看到Button不會(huì)收到Cancel事件。即時(shí)手指滑出了button,仍然可以收到Move和Up事件。

ACTION_CANCEL作用

我們知道如果某一個(gè)子View處理了Down事件,那么隨之而來(lái)的Move和Up事件也會(huì)交給它處理。但是交給它處理之前,父View還是可以攔截事件的,如果攔截了事件,那么子View就會(huì)收到一個(gè)Cancel事件,并且不會(huì)收到后續(xù)的Move和Up事件。
下面我們通過(guò)源碼驗(yàn)證上面這段話:

@Override public boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = false;final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {//----------源碼1-----------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;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {// ...}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// ...} else {// ---------------源碼2----------------// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//-------------- 源碼3---------if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}//-------------- 源碼4---------if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}return handled; }

這段代碼進(jìn)行了一定的精簡(jiǎn),但是大致流程還是很清楚的。當(dāng)子view處理完Down事件之后,mFirstTouchTarget不為null,那么是可以走到源碼1處的,如果子view沒(méi)有對(duì)ViewGroup進(jìn)行不攔截的設(shè)置,那么disallowIntercept為false,此時(shí)會(huì)走到onInterceptTouchEvent方法,如果我們攔截了Move事件,那么onInterceptTouchEvent返回true。也就是說(shuō)intercepted為true;
此時(shí)會(huì)走到源碼2處,遍歷每一個(gè)將要處理事件的view,alreadyDispatchedToNewTouchTarget表示事件是否已經(jīng)交給view處理,此時(shí)當(dāng)然是false。到源碼3處時(shí),由于intercepted為true,所以cancelChild為true,我們看下dispatchTransformedTouchEvent方法:

/*** Transforms a motion event into the coordinate space of a particular child view,* filters out irrelevant pointer ids, and overrides its action if necessary.* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.*/private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case. We don't need to perform any transformations// or filtering. The important part is the action, not the contents.final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// 。。。}

注意,走到dispatchTransformedTouchEvent時(shí),cancel這個(gè)參數(shù)為true。通過(guò)event.setAction(MotionEvent.ACTION_CANCEL);這句話將MotionEvent的Action設(shè)置為了ACTION_CANCEL,并且交給子view處理。

通過(guò)這里我源碼我們知道了ACTION_CANCEL的產(chǎn)生過(guò)程。那么為什么產(chǎn)生ACTION_CANCEL后,子view無(wú)法收到后續(xù)事件了呢?

在源碼4處,有這樣一段代碼:

if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}

cancelChild為true,會(huì)將當(dāng)前target節(jié)點(diǎn)從鏈表中刪除。那么后續(xù)事件到來(lái)時(shí),view在mFirstTouchTarget鏈表中不存在,自然就不會(huì)交給它處理了。

FLAG_DISALLOW_INTERCEPT的作用

我們通過(guò)上面的代碼可以看出,即時(shí)是MOVE和UP事件,在傳遞給子View之前也是可以通過(guò)ViewGroup的onInterceptTouchEvent方法攔截的,如果攔截了,那么該事件就會(huì)變成Cancel事件傳遞給子view。
那么是否有辦法,子view不讓ViewGroup攔截時(shí)間呢?

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;}

可以看到disallowIntercept變量為true的時(shí)候,會(huì)跳過(guò)onInterceptTouchEvent方法。換句話如果設(shè)置了FLAG_DISALLOW_INTERCEPT這個(gè)flag,那么ViewGoup則不會(huì)攔截Move和Up事件。

我們?cè)僬业皆O(shè)置FLAG_DISALLOW_INTERCEPT這個(gè)flag的地方。

@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parentif (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}}

這段代碼,充分展示了位運(yùn)算的強(qiáng)大即高效。子View可以通過(guò)設(shè)置requestDisallowInterceptTouchEvent(true)來(lái)達(dá)到禁止父ViewGroup攔截事件的目的。

但是需要注意的是,FLAG_DISALLOW_INTERCEPT這個(gè)flag無(wú)法對(duì)Down事件生效。因?yàn)樵贒own時(shí),會(huì)清空FLAG_DISALLOW_INTERCEPT。

// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);resetTouchState();}/*** Resets all touch state in preparation for a new cycle.*/private void resetTouchState() {clearTouchTargets();resetCancelNextUpFlag(this);mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;mNestedScrollAxes = SCROLL_AXIS_NONE;}

總結(jié)

以上是生活随笔為你收集整理的Android事件分发之ACTION_CANCEL机制及作用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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