事件分發(fā)機(jī)制一直是Android中的一個(gè)重難點(diǎn),最近也正好有點(diǎn)時(shí)間,于是決定好好研究一下這個(gè)東西,順便也寫下來(lái)。
首先我們來(lái)看一下當(dāng)點(diǎn)擊一個(gè)處在一個(gè)layout中的button時(shí),事件究竟是怎么最后被button處理的。這里我分別用一個(gè)MyLayout和MyButton分別繼承LinearLayout和Button,代碼如下:
MyLayout.java
public class MyLayout extends LinearLayout {
private final String TAG = getClass().getName();
public MyLayout(Context context) {
super(context);
}
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "on touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "on touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "on touch event layout up");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "intercept touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "intercept touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "intercept touch event layout up");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatch touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatch touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatch touch event layout up");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
}
MyButton.java
public class MyButton extends Button {
private final String TAG = getClass().getName();
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatch touch event button down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatch touch event button move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatch touch event button up");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "on touch event button down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "on touch event button move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "on touch event button up");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
這里我們重寫了LinearLayout和Button的dispatchTouchEvent和onTouchEvent還有LinearLayout的onInterceptTouchEvent。
現(xiàn)在我們運(yùn)行程序并點(diǎn)擊button,看看打印的日志都有那些內(nèi)容:
MyLayout: dispatch touch event layout down
MyLayout: intercept touch event layout down
MyButton: dispatch touch event button down
MyButton: on touch event button down
MyLayout: dispatch touch event layout move
MyLayout: intercept touch event layout move
MyButton: dispatch touch event button move
MyButton: on touch event button move
MyLayout: dispatch touch event layout up
MyLayout: intercept touch event layout up
MyButton: dispatch touch event button up
MyButton: on touch event button up
從日志中我們可以看出當(dāng)我們點(diǎn)擊的動(dòng)作被捕獲后,點(diǎn)擊事件的分發(fā)順序?yàn)椋?br>
父View的dispatchTouchEvent->父View的onInterceptTouchEvent->子View的dispatchTouchEvent->子View的onTouchEvent最后處理事件。
那么現(xiàn)在問(wèn)題來(lái)了,當(dāng)我們的點(diǎn)擊事件被捕獲之后,究竟經(jīng)過(guò)了什么過(guò)程使得事件最終到達(dá)我們的目標(biāo)View并被處理的,另外我們是否可以根據(jù)我們想要的效果對(duì)這個(gè)過(guò)程進(jìn)行自定義呢?接下來(lái)就來(lái)小小地研究一下這個(gè)過(guò)程。
過(guò)程分析
這一篇我們先不深入源碼進(jìn)行分析,因?yàn)樯钊朐创a是個(gè)比較復(fù)雜的過(guò)程,容易被繞進(jìn)其中,因此這個(gè)內(nèi)容在下一篇進(jìn)行分析。我們先來(lái)看看能不能通過(guò)其他的方式把這個(gè)過(guò)程大概的弄清楚。
先來(lái)看一下官方API對(duì)這三個(gè)方法的描述:
dispatchTouchEvent:Pass the touch screen motion event down to the target view, or this view if it is the target.
onInterceptTouchEvent:Implement this method to intercept all touch screen motion events.This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.
onTouchEvent:Implement this method to handle touch screen motion events.
官方的對(duì)這三個(gè)方法的解釋十分清楚:dispatchTouchEvent負(fù)責(zé)分配屏幕事件究竟是由自己處理還是向下傳遞給它的子View進(jìn)行處理;onInterceptTouchEvent負(fù)責(zé)當(dāng)屏幕事件從父View向子View傳遞的時(shí)候進(jìn)行攔截,我們可以通過(guò)這個(gè)方法監(jiān)視并研究事件向下傳遞的過(guò)程;onTouchEvent負(fù)責(zé)最后屏幕事件的處理。
剛剛我們?cè)诖蛴∪罩镜倪^(guò)程中發(fā)現(xiàn)點(diǎn)擊事件并沒(méi)有經(jīng)過(guò)MyLayout的onTouchEvent,從剛剛的官方的解釋中我們知道onTouchEvent是用于處理屏幕事件的,而日志中可以看出屏幕事件最后被我們安排在MyLayout中的子View——MyButton處理了。
那么現(xiàn)在我們想要對(duì)這個(gè)過(guò)程進(jìn)行簡(jiǎn)單的分析和處理有需要知道點(diǎn)什么呢?我們?cè)賮?lái)看看官方對(duì)這三個(gè)方法的布爾類型的返回值的解釋是什么樣的:
dispatchTouchEvent:True if the event was handled by the view, false otherwise.
onInterceptTouchEvent:Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent().
onTouchEvent:True if the event was handled, false otherwise.
這三個(gè)返回值的解釋是這樣的:dispatchTouchEvent:若返回true則由自己分發(fā)屏幕事件,否則傳遞給子View處理;onInterceptTouchEvent:若返回true則將傳遞給子View的屏幕事件攔截下,并交給自己的onTouchEvent處理;onTouchEvent:若返回true則代表屏幕事件被自己處理了,反之則不是。
看到這里好像有點(diǎn)意思了,原來(lái)事件處理的過(guò)程中這三個(gè)方法的返回值起到了相當(dāng)關(guān)鍵的作用,現(xiàn)在我們稍微修改一下MyLayout看看有什么效果。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatch touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatch touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatch touch event layout up");
break;
default:
break;
}
return false;
}
這里將dispatchTouchEvent的返回值設(shè)置為false,然后打印日志:
MyLayout: dispatch touch event layout down
只有一條,說(shuō)明當(dāng)MyLayout捕獲了ACTION_DOWN后dispatchTouchEvent方法返回了false,然后緊接其后的其他事件就不再有MyLayout進(jìn)行分配處理了,那么后面的事件都上哪里進(jìn)行處理了呢?我們將MyLayout的dispatchTouchEvent的返回值改回去,再改改MyButton中的dispatchTouchEvent。
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatch touch event button down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatch touch event button move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatch touch event button up");
break;
default:
break;
}
return false;
}
接著打印日志:
MyLayout: dispatch touch event layout down
MyLayout: intercept touch event layout down
MyButton: dispatch touch event button down
MyLayout: on touch event layout down
我們發(fā)現(xiàn)當(dāng)dispatchTouchEvent返回了false之后,則由它的父View處理ACTION_DOWN事件,但全過(guò)程只是處理了ACTION_DOWN,剩下的去哪里了呢?這時(shí)候我們想起官方對(duì)onTouchEvent的返回值的解釋,我們來(lái)修改一下MyLayout的onTouchEvent的返回值。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "on touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "on touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "on touch event layout up");
break;
default:
break;
}
return false;
}
我們將返回值設(shè)為false,但是打印日志后發(fā)現(xiàn)并沒(méi)有任何改變,我們?cè)賹⑺O(shè)為true試試。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "on touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "on touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "on touch event layout up");
break;
default:
break;
}
return true;
}
這時(shí)候我們通過(guò)打印日志有了一個(gè)大發(fā)現(xiàn):
MyLayout: dispatch touch event layout down
MyLayout: intercept touch event layout down
MyButton: dispatch touch event button down
MyLayout: on touch event layout down
MyLayout: dispatch touch event layout move
MyLayout: on touch event layout move
MyLayout: dispatch touch event layout up
MyLayout: on touch event layout up
原來(lái)當(dāng)onTouchEvent返回false的時(shí)候,后續(xù)屏幕事件便不會(huì)再被處理。相當(dāng)于做了一個(gè)標(biāo)記,只有當(dāng)這個(gè)標(biāo)記為true時(shí),ACTION_DOWN之后的一系列屏幕事件之后被處理。
現(xiàn)在我們大致弄懂了dispatchTouchEvent和onTouchEvent的作用,接下來(lái)我們?cè)賮?lái)看看onInterceptTouchEvent,看看事件是怎樣由ViewGroup傳遞給子View的。
我們將MyLayout的onInterceptTouchEvent的返回值改為true:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "intercept touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "intercept touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "intercept touch event layout up");
break;
default:
break;
}
return true;
}
這時(shí)候的日志內(nèi)容是這樣的:
MyLayout: dispatch touch event layout down
MyLayout: intercept touch event layout down
MyLayout: on touch event layout down
通過(guò)日志我們發(fā)現(xiàn),本應(yīng)該傳遞給子View的事件被onInterceptTouchEvent攔截并返回true之后交由MyLayout本身的onTouchEvent去處理了。接下來(lái)我們將返回值設(shè)為false看看:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "intercept touch event layout down");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "intercept touch event layout move");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "intercept touch event layout up");
break;
default:
break;
}
return false;
}
現(xiàn)在的日志就和沒(méi)有做任何修改的時(shí)候一樣了:
MyLayout: dispatch touch event layout down
MyLayout: intercept touch event layout down
MyButton: dispatch touch event button down
MyButton: on touch event button down
MyLayout: dispatch touch event layout move
MyLayout: intercept touch event layout move
MyButton: dispatch touch event button move
MyButton: on touch event button move
MyLayout: dispatch touch event layout up
MyLayout: intercept touch event layout up
MyButton: dispatch touch event button up
MyButton: on touch event button up
現(xiàn)在我們懂了onInterceptTouchEvent在父View向子View傳遞事件的過(guò)程中起到的作用就是決定這個(gè)事件是否是由本View接下處理還是傳遞給子View進(jìn)行處理,當(dāng)方法返回true的時(shí)候就交由本View處理,而返回false的時(shí)候就交由子View進(jìn)行處理。
總結(jié)
現(xiàn)在我們基本弄懂了這三個(gè)方法在事件分發(fā)過(guò)程中各自起到的作用,下面我們?cè)僦匦率崂硪幌翧ndroid事件分發(fā)的過(guò)程。
首先事件在傳遞過(guò)程中交由dispatchTouchEvent進(jìn)行事件的分配,若dispatchTouchEvent返回false,則將事件傳遞回其父View,讓其父View的onTouchEvent進(jìn)行處理;若返回true,則將事件傳遞給onInterceptTouchEvent。當(dāng)onInterceptTouchEvent接收到事件之后,它也會(huì)有一個(gè)布爾類型的返回值,若返回true,則事件不會(huì)繼續(xù)向下傳遞而是被該View攔截下,交由該View的onTouchEvent進(jìn)行處理;若返回false,則會(huì)將事件傳遞給其子View進(jìn)行處理。最后,當(dāng)目標(biāo)View接收到這一系列屏幕事件的時(shí)候,首先會(huì)去處理ACTION_DOWN事件,然后會(huì)由一個(gè)返回值,若返回值為true,則表明以ACTION_DOWN為開始的一些列事件目標(biāo)View都會(huì)接下并處理;但若是返回false,則后續(xù)的一系列事件,整個(gè)程序都不會(huì)將其接下并處理。
我們通過(guò)不停地嘗試,最終基本弄清楚了Android事件分發(fā)的流程,但是這個(gè)過(guò)程中究竟發(fā)生了什么,這個(gè)過(guò)程到底是怎么進(jìn)行的,還需深入View的源碼進(jìn)行探究,以后我會(huì)尋找時(shí)間認(rèn)真研究一下,并將我的學(xué)習(xí)成果分享出來(lái)。