Android 事件分發(fā)一直都是一個(gè)難點(diǎn),讓人很模糊,感覺自己知道點(diǎn),但又不是很清楚。最重要的是我知道了理論,怎么沒感覺在實(shí)際開發(fā)中用到呢?今天決定來好好研究一下這個(gè)事件分發(fā)機(jī)制,如有不正確的地方,請(qǐng)多多指教。

首先看上圖,我們要知道這個(gè)層次關(guān)系,你可以把每一層理解為一張紙放在桌子上,那就是Activity這張紙?jiān)谧钕旅?,ViewGroup在Activity的上面,View在ViewGroup的上面。
好了,理解完層次關(guān)系,我們可以點(diǎn)一下View內(nèi)任意一點(diǎn)。這時(shí)View是接受了一個(gè)事件(先不用管是什么事件,隨便你就理解為一個(gè)ACTION_DOWN事件)。到這里我要說下我原來的錯(cuò)誤認(rèn)識(shí),做個(gè)反面教程,讓大家更好的理解。我原來認(rèn)為,View最先接收到到事件,所以事件的傳遞肯定是View---ViewGroup---Activity這樣由外往里傳遞的。
其實(shí)不然,你可以這樣理解。Activity、ViewGroup、View是一個(gè)部門的三個(gè)同事,只不過Activity是部門主任,ViewGroup是主管,View是一個(gè)職員。
突然有一天View接受到一個(gè)任務(wù)(觸摸事件),那我們要想一下這個(gè)任務(wù)是怎么來的呢,無論這個(gè)任務(wù)是你自己部門內(nèi)部的,還是兄弟部門的協(xié)作任務(wù)。只要給你分配任務(wù),那都要必須經(jīng)過你的主任和主管的同意,因?yàn)樗麄兪悄愕捻旑^上司,而你是他們的干將。所以說這個(gè)任務(wù)也就是這個(gè)事件,在傳到View之前,早已經(jīng)經(jīng)過了你的領(lǐng)導(dǎo)的同意,也就是早就經(jīng)過了Activity和ViewGroup。如果領(lǐng)導(dǎo)不同意,或是半路領(lǐng)導(dǎo)就一句話的事幫你把事給解決了。那這個(gè)任務(wù)也就不會(huì)傳遞到View這來了。
總結(jié):雖然我們是對(duì)View發(fā)起了觸摸事件,但是事件的傳遞開始于Activity。由Activiy----ViewGroup---View。
好了,到這里大體知道了事件傳遞流程了。但是我們還要知道幾個(gè)重要的方法。如下圖所示,除了ViewGroup多了一個(gè)onInterceptTouchEvent方法外,他們都有dispatchTouchEvent和onTouchEvent。

接下來我們看看這幾個(gè)方法都是什么意思,還是說下我原來的認(rèn)識(shí)誤區(qū)。我原來認(rèn)為dispatchTouchEvent是事件分發(fā)的方法,所以它返回true代表向下分發(fā)事件,false代表不向下分發(fā),如果return super. dispatchTouchEvent(),我就認(rèn)為交給父類處理。
我當(dāng)時(shí)的認(rèn)知是不完全正確的,那說下這幾個(gè)方法分別返回 true或false或是return super.xxx到底是什么意思。
- dispatchTouchEvent
true:代表消費(fèi)掉事件,不再向下傳遞
false:代表不向下分發(fā)事件,不處理,再把事件返還到上一層
super:表示繼續(xù)向下傳遞 - onInterceptTouchEvent
true: 代表攔截事件,不向下傳遞,交給自己的onTouchEvent處理
false和super:都表示不攔截,繼續(xù)向下傳遞事件 - onTouchEvent:
true:表示消費(fèi)掉事件
false:Activity中表示丟棄事件不處理,而如果是在ViewGroup和View中表示自己不處理,交給上級(jí)
super:交給上級(jí)處理
這里還要區(qū)分是誰的dispatchTouchEvent方法,Activity和View的這個(gè)方法同樣的返回值,所做的操作是有區(qū)別的。詳細(xì)可以看下面的流程圖
是時(shí)候展示真正的技術(shù)了:我看了好多類似的事件分發(fā)流程圖,乍眼一看都有點(diǎn)蒙圈,覺得好凌亂復(fù)雜。所以我也畫了一個(gè)圖,個(gè)人認(rèn)為還算清晰簡潔。

你不需要太仔細(xì)看這個(gè)圖,先簡單的看下。然后就是需要自己擼代碼驗(yàn)證的時(shí)刻了,這時(shí)會(huì)加深你的理解。擼代碼回來再仔細(xì)看,你就會(huì)恍然大明白了。
那怎樣寫代碼,很簡單:
- 先自定義一個(gè)ViewGroup繼承LinearLayout,重寫需要的幾個(gè)方法
- 自定義View繼承TextView,重寫需要的方法
- 在Activity的布局代碼中加入你自定義的ViewGroup,ViewGroup里面是的自定義的TextView。Activity中重寫需要的方法。
//自定義ViewGroup
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/*************下面是我們需要的方法*************/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("ViewGroup", "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("ViewGroup", "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewGroup", "onTouchEvent");
return super.onTouchEvent(event);
}
}
//自定義TextView
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/*************下面是我們需要的方法*************/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("View", "dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("View", "onTouchEvent");
return super.onTouchEvent(event);
}
}
//Activity中的代碼
public class Main6Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main6);
}
// 這里只打印ACTION_DOWN的傳遞,不然會(huì)有移動(dòng)和抬起的事件,在一起比較亂
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_DOWN){
Log.e("Activity", "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("Activity", "onTouchEvent");
return super.onTouchEvent(event);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zsl.aa.Main6Activity">
<com.zsl.aa.MyViewGroup
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.zsl.aa.MyTextView
android:id="@+id/tv_view"
android:layout_width="120dp"
android:layout_height="130dp"
android:background="#ff00ff" />
</com.zsl.aa.MyViewGroup>
</LinearLayout>
ok,到這里準(zhǔn)備工作就完事了,非常簡單的代碼。運(yùn)行程序后,點(diǎn)下View看看打印結(jié)果。然后再分不同的情況,分別更改返回的值為true或false或是super.xxx。分別看看打印情況,你就會(huì)知道了傳遞流程了。