關(guān)于Android事件傳遞機(jī)制的文章有很多,但是每篇文章你看過之后也許有點(diǎn)是懂非懂,本篇文章讓你輕松了解Android控件的事件傳遞機(jī)制.
觸摸事件類型
觸摸事件對(duì)應(yīng)的是MotionEvent,事件主要有如下三種
ACTION_DOWN:用戶手指按下的操作,標(biāo)志一次觸摸事件的開始
ACITON_MOVE:用戶在手指按下屏幕之后,在松開之前,如果移動(dòng)的距離超過一定的閾值,就會(huì)被認(rèn)定為ACTION_MOVE操作
ACTION_UP:用戶手指抬起的操作,標(biāo)志一次觸摸事件的結(jié)束
觸摸事件傳遞
講完了事件,接下來(lái)我們說(shuō)分發(fā),事件的分發(fā),實(shí)質(zhì)就是講事件的傳遞,事件的傳遞分為三個(gè)階段:
分發(fā):dispatchTouchEvent(MotionEvent event),在Android系統(tǒng)中,所有的觸摸事件都是通過這個(gè)方法來(lái)分發(fā)的
攔截:onInterceptTouchEvent,這個(gè)方法只有ViewGroup中存在
消費(fèi):onTouchEvent
觸摸事件的相關(guān)類
在android系統(tǒng)中,擁有事件傳遞處理能力的類有三個(gè)
Activity:擁有dispatchTouchEvent和onTouchEvent方法
View:和Activity一樣同時(shí)擁有dispatchTouchEvent和onTouchEvent方法
ViewGourp:擁有dispatchTouchEvent和onTouchEvent方法,在加上一個(gè)攔截方法onInterceptTouchEvent
View和ViewGourp之間的關(guān)系
View是ViewGourp的父類,一個(gè)ViewGroup中可以擁有多個(gè)View和ViewGourp,下面谷歌android API 文檔的一個(gè)截圖,可以清楚的看到他們之間的一個(gè)繼承關(guān)系

前面我在講事件傳遞的時(shí)候,主要講了三個(gè)方法,事件分發(fā)dispathTouchEvent方法,事件攔截的onInterecptTouchEvent方法和事件消費(fèi)的onTouch的onTouchEvent方法。



以上就是三個(gè)關(guān)于觸摸事件的處理方法,我們通過谷歌官方api文檔的截圖我們不難發(fā)現(xiàn),他們都是返回的是一個(gè)boolean類型的常量,無(wú)外乎要么返回true,要么返回false,還有一種可能就是調(diào)用父類的同名方法,通過接口文檔我們得知dispathTouchEvent返回true表示該事件被View處理了,反之返回false就是不處理,不處理那么事件最后交給誰(shuí)了?onTouchEvent方法返回true這是表示被消費(fèi)掉了,反之亦然,那么調(diào)用它父類的同名方法結(jié)果又會(huì)怎么樣?關(guān)于攔截方法如果返回true表示被攔截掉了,那么這個(gè)事件是交給了自己的onTouchEvent方法了嗎?如果onTouchEvent返回了false不消費(fèi)掉這給事件又會(huì)交給誰(shuí)去處理?等等一系列的疑問。
但是不管怎么樣我們通過官方文檔我們可以得到一個(gè)結(jié)論就是:不同的返回值會(huì)導(dǎo)致事件的傳遞流程發(fā)生很大的變化,我們想了,我們通過不斷的修改這些方法的返回值,并查看日志記錄,我們就可以清楚的看到每一種情況觸摸事件的處理流程。
為此我準(zhǔn)備了兩個(gè)自定義控件CusLayout和CusButton,并復(fù)寫他們的dispathchTouchEvent方法和onTouchEvent方法,以及onInterceptTouEvent方法,不斷的修改他們的返回值,來(lái)跟蹤每個(gè)事件的處理流程。
先上代碼看示例,再講原理:
自定義ViewGroup CusLayout.java
package com.zjx.vcar.test.view;
public class CusLayout extends LinearLayout {
public static final String TAG = "MYTAG";
public CusLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusLayout dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusLayout onInterceptTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"CusLayout onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
自定義View CusButton.java
@SuppressLint("AppCompatCustomView")
public class CusButton extends Button {
public static final String TAG = "MYTAG";
public CusButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"CusButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
創(chuàng)建MainActivty.java的布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.touchevent.view.CusLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#FF00FF"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.touchevent.view.CusButton
android:layout_width="200dp"
android:layout_height="100dp"
android:text="點(diǎn)擊測(cè)試"
android:background="#00FF00"
/>
</com.android.touchevent.view.CusLayout>
創(chuàng)建MainActivity.java
public class MainActivity extends AppCompatActivity {
public static String TAG = "MYTAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MainActivity dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MainActivity onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
好了,截止目前測(cè)試的準(zhǔn)備工作就做好了,我們看下頁(yè)面效果,CusLayout設(shè)置了紫色背景,CusButon設(shè)置了綠色背景
本文我們只分析CusButton的處理流程,CusLayout和Activity的流程我們放在后面的章節(jié)進(jìn)行介紹,這樣是為了避免文章過長(zhǎng),反而不利于大家的學(xué)習(xí)。
CusButton的onTouchEvent方法
情況1:我們onTouchEvent調(diào)用父類的同名方法
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"CusButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
點(diǎn)擊測(cè)試按鈕測(cè)試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_MOVE
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: CusButton onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusButton onTouchEvent start:ACTION_UP
CusButton 的onTouchEvent方法依次接受到了三個(gè)事件:ACTION_DOWN,ACTION_MOVE,ACTION_MOVE
情況2:我們onTouchEvent返回true
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"CusButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return true;
}
點(diǎn)擊測(cè)試按鈕測(cè)試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton onTouchEvent start:ACTION_DOWN
-----------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusButton onTouchEvent start:ACTION_UP
通過測(cè)試打印的log我們不難發(fā)現(xiàn),調(diào)用父類的同名方法和返回true其結(jié)果是一樣的
情況3:返回false
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"CusButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return false;
}
點(diǎn)擊測(cè)試結(jié)果:
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton onTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onTouchEvent start:ACTION_DOWN
V/MYTAG: MainActivity onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MainActivity onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MainActivity onTouchEvent start:ACTION_UP
我們驚奇的發(fā)現(xiàn)如果返回false,CusButton的onTouchEvent方法將事件傳遞給CusLayout的onTourchEvent方法了,也就是事件回傳了,如果CusLayout不消費(fèi)的話,將繼續(xù)回傳,直到MainActivity的onTouchEvent方法,后續(xù)的ACTION_MOVE,ACTION_UP,就不在分發(fā)了,MainActivity的onTouchEvent就直接消費(fèi)掉了。
CusButton的dispatchTouchEvent方法
情況1:dispatchTouchEvent調(diào)用父類的同名方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
點(diǎn)擊測(cè)試結(jié)果我們不用測(cè)試就能猜得到了,因?yàn)槲覀儨y(cè)試它的onTouchEvent方法的時(shí)候,dispatchTouchEvent方法默認(rèn)就調(diào)用的是父類的同名方法super.dispatchTouchEvent(ev),我們通過觀察打印日志不難發(fā)現(xiàn),CusButton的dispatchTouchEvent方法執(zhí)行完畢,就把點(diǎn)擊事件傳遞給自己的onTouchEvent方法了,也就是說(shuō)CusButton沒有子view了,就把事件分發(fā)給自己onTouchEvent方法了
情況2:返回true
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return true;
}
點(diǎn)擊測(cè)試按鈕
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_DOWN
------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_UP
返回true,點(diǎn)擊事件被CusButton的dispatchTouchEvent方法自行消費(fèi)掉了
情況3:返回false
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"CusButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return false;
}
點(diǎn)擊測(cè)試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: CusButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: CusLayout onTouchEvent start:ACTION_DOWN
V/MYTAG: MainActivity onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MainActivity onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MainActivity onTouchEvent start:ACTION_UP
當(dāng)CusButton的dispatchTouchEvent方法返回false的時(shí)候,點(diǎn)擊事件是一路回傳,直到到達(dá)MainActivity onTouchEvent的方法才算結(jié)束
好了,直到現(xiàn)在我們對(duì)CusButton的dispatchTouchEvent方法和onTouchEvent方法就徹底分析完了,我們不難得出結(jié)論:觸摸事件是按照View樹的嵌套層次由內(nèi)到外依次進(jìn)行傳遞,直到到達(dá)最內(nèi)層的子View的onTouchEvent方法,如果onTouchEvent方法返回true或者調(diào)用了父類的同名方法,表示該事件被消費(fèi)掉了,如果返回了false則該觸摸事件會(huì)回傳回去,直到到達(dá)最外層的MainActivity的onTouchEvent方法,對(duì)于dispatchTouchEvent方法如果調(diào)用的是父類的同名方法則觸摸事件會(huì)分發(fā)給自己的onTouchEvent方法,如果返回了true則表示該方法被自行消費(fèi)掉了,如果返回了false表示該觸摸事件會(huì)一層一層的回傳給自己父View的onTouchEvent方法,直到到達(dá)MainActivity的onTouchEvent方法才算結(jié)束,此后的其他事件如ACTION_MOVE和ACTION_DOWN事件直接就被MainActivity的onTouchEvent方法消費(fèi)掉了,而不會(huì)傳遞給CusLayout了