Android事件分发之ACTION_CANCEL机制及作用
目錄
- ACTION_CANCEL產(chǎn)生場景
- ACTION_CANCEL作用
- FLAG_DISALLOW_INTERCEPT的作用
如果要查看ACTION_MOVE與ACTION_UP的事件傳遞機制,查看Android事件分發(fā)之ACTION_MOVE與ACTION_UP的傳遞機制
ACTION_CANCEL產(chǎn)生場景
在閱讀ViewGroup事件分發(fā)相關(guān)源碼過程中,有時候會見到ACTION_CANCEL這一事件。那么這一事件是如何產(chǎn)生的呢?按照網(wǎng)上的說法,當(dāng)手指從當(dāng)前view移出后,當(dāng)前view就會收到ACTION_CANCEL這一事件,這一定是正確的嗎?下面我們來看兩個例子:
例子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)}}我們自定義一個ViewGroup,覆蓋它的onInterceptTouchEvent方法和dispatchTouchEvent方法。在onInterceptTouchEvent方法中我們只攔截了一次MotionEvent.ACTION_MOVE事件。hasInterceptMoveEvent用于控制只攔截一次。而在dispatchTouchEvent方法中,我們打印出當(dāng)前ViewGroup處理的事件??聪耮etAction方法定義:
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"} }其實就是根據(jù)Action對應(yīng)的Int值轉(zhuǎn)化為字符串,讓我們的Log更加直觀。
而在ViewGroup內(nèi)部,我們放置了一個自定義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} }這個自定義Button只是將當(dāng)前dispatchTouchEvent方法收到的事件打印出來。
現(xiàn)在我們進行如下操作:
手指按住button,然后移動,移出button外,然后松開。我們看下打印出的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確實收到了一個ACTION_CANCEL事件,并且在這個事件之后,CusButton并沒有收到任何事件。所以我們大概能夠猜到:如果ViewGroup攔截了Move事件,那么這個Move事件將會轉(zhuǎn)化為Cancel事件傳遞給子view。
例子2:
如果我們的ViewGroup不攔截Move事件,那么是否也會產(chǎn)生Cancel事件呢?
我們修改一下CustomViewGroup源碼:
即onInterceptTouchEvent方法不攔截事件。同樣的操作我們再來看下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不會收到Cancel事件。即時手指滑出了button,仍然可以收到Move和Up事件。
ACTION_CANCEL作用
我們知道如果某一個子View處理了Down事件,那么隨之而來的Move和Up事件也會交給它處理。但是交給它處理之前,父View還是可以攔截事件的,如果攔截了事件,那么子View就會收到一個Cancel事件,并且不會收到后續(xù)的Move和Up事件。
下面我們通過源碼驗證上面這段話:
這段代碼進行了一定的精簡,但是大致流程還是很清楚的。當(dāng)子view處理完Down事件之后,mFirstTouchTarget不為null,那么是可以走到源碼1處的,如果子view沒有對ViewGroup進行不攔截的設(shè)置,那么disallowIntercept為false,此時會走到onInterceptTouchEvent方法,如果我們攔截了Move事件,那么onInterceptTouchEvent返回true。也就是說intercepted為true;
此時會走到源碼2處,遍歷每一個將要處理事件的view,alreadyDispatchedToNewTouchTarget表示事件是否已經(jīng)交給view處理,此時當(dāng)然是false。到源碼3處時,由于intercepted為true,所以cancelChild為true,我們看下dispatchTransformedTouchEvent方法:
注意,走到dispatchTransformedTouchEvent時,cancel這個參數(shù)為true。通過event.setAction(MotionEvent.ACTION_CANCEL);這句話將MotionEvent的Action設(shè)置為了ACTION_CANCEL,并且交給子view處理。
通過這里我源碼我們知道了ACTION_CANCEL的產(chǎn)生過程。那么為什么產(chǎn)生ACTION_CANCEL后,子view無法收到后續(xù)事件了呢?
在源碼4處,有這樣一段代碼:
if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}cancelChild為true,會將當(dāng)前target節(jié)點從鏈表中刪除。那么后續(xù)事件到來時,view在mFirstTouchTarget鏈表中不存在,自然就不會交給它處理了。
FLAG_DISALLOW_INTERCEPT的作用
我們通過上面的代碼可以看出,即時是MOVE和UP事件,在傳遞給子View之前也是可以通過ViewGroup的onInterceptTouchEvent方法攔截的,如果攔截了,那么該事件就會變成Cancel事件傳遞給子view。
那么是否有辦法,子view不讓ViewGroup攔截時間呢?
可以看到disallowIntercept變量為true的時候,會跳過onInterceptTouchEvent方法。換句話如果設(shè)置了FLAG_DISALLOW_INTERCEPT這個flag,那么ViewGoup則不會攔截Move和Up事件。
我們再找到設(shè)置FLAG_DISALLOW_INTERCEPT這個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);}}這段代碼,充分展示了位運算的強大即高效。子View可以通過設(shè)置requestDisallowInterceptTouchEvent(true)來達到禁止父ViewGroup攔截事件的目的。
但是需要注意的是,FLAG_DISALLOW_INTERCEPT這個flag無法對Down事件生效。因為在Down時,會清空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机制及作用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件调试基础
- 下一篇: 核芯模数转换器新品CL1680,对标AD