Android事件分发之ACTION_CANCEL机制及作用
目錄
- 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源碼:
即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)證上面這段話:
這段代碼進(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方法:
注意,走到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í)間呢?
可以看到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)題。
- 上一篇: 软件调试基础
- 下一篇: Android开发之多级下拉列表菜单实现