以前沒太注意,很久沒用之后再使用發(fā)現(xiàn)有些地方模糊了,就是那種不知道是對是錯的感覺,然后又要重復(fù)上網(wǎng)去找資料,所以打算自己整理一篇,有很多時候,一些特殊的需要要是能巧妙的運用事件分發(fā)機(jī)制其實能很快的去解決問題。
一.流程
1.打印全流程
對于activity,viewgroup和view來說,如果不再任意一個流程消費事件,就會打印出這個結(jié)果。

這個就不用多解釋了,如果都沒消費事件,會在最后一句打印出ACTION_DOWN沒有被處理。
2.圖解過程
由上面打印的過程可以做出下面一張事件分發(fā)時的流程圖:

但是在activity,viewgroup和view的dispatchTouchEvent、onTouchEvent這些方法中,返回值是一個布爾類型的,有三種情況,false,true和super,分別對這三種情況和分發(fā)流程進(jìn)行探究后得到下圖:

圖太麻煩了,我就不重新畫了,從網(wǎng)上找了一張,不同的是,圖中的onTouchEvent,我試過,如果傳的是super,是會被消費的,而不是返回上一層。
3.一般情況下的事件分發(fā)
上面的情況我是重寫viewgroup重寫view去重寫onTouchEvent和dispatchTouchEvent方法,但是實際操作中不會總是這種情況,因為我們不可能把接觸到事件分發(fā)需求的控件都重寫,那樣就太麻煩了。所以先來看看一般情況下的分發(fā)情況。
我把自定義view換成普通的view然后寫onClick方法,打印以下結(jié)果

發(fā)現(xiàn)在onClick事件中,view會消費事件,即便你在onClick中沒做什么操作,事件也會被view給消費,那不寫onClick方法呢?

發(fā)現(xiàn)打印結(jié)果中,即便沒對View監(jiān)聽,事件也不會往上傳,然后我打算看看view中的源碼

我* , 看來我本地是看不了了,別的地方找找。如果你看到了源碼,你會發(fā)現(xiàn)臥槽真尼瑪多,一大堆判斷。我就找來了一個別人整理過的所寫版的。感謝這位大神,很有良心。
if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
...
break;
...
}
return true;
}
看得出默認(rèn)的情況下,返回true也就是被消費。我看了源碼就知道了,當(dāng)然沒貼出來,這里設(shè)置setClickable的話就會有神奇的效果,我設(shè)置view.setClickable(false);
打印出下面的結(jié)果。

所以能得出一個簡單的結(jié)論,一般情況下你不從寫view,要讓這個view的事件往上層分發(fā),需要設(shè)置setClickable(false)
那有的朋友說,我要有那種點擊view之后,view先做操作,然后viewgroup在做操作,而且還不是自定義view和viewgrou的條件下。如果是直接對View設(shè)置onClickListener的話是無法達(dá)到這個效果的,所以只能對view設(shè)置OnTouchListener
btnContent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
Log.v("mmp", "view->setOnTouchListener");
}
return false;
}
});
這樣就能先執(zhí)行view的點擊事件,再執(zhí)行viewgroup的點擊事件。

4.總結(jié)
事件分發(fā)有意思的地方就在于,你想讓什么去觸發(fā)這個事件,并且是否分發(fā)給上一層去做處理,更有意思的地方在于這個機(jī)制是先向下分發(fā),由activity分發(fā)給viewgroup再分發(fā)給view,之后執(zhí)行是向上執(zhí)行,先由view執(zhí)行。所以它使用起來會很靈活,比如說你想做一系列的點擊事件,點擊一個按鈕后activity先執(zhí)行某步操作,view再執(zhí)行某步操作,然后activity再執(zhí)行某部操作,這個做法也是可以做到的。
二.事件分發(fā)的靈活用法
事件分發(fā)他是一個機(jī)制,所以它可以適用于很多的場景,不要說它只能用于處理特殊的點擊事件,那可真是暴殄天物。
1.防止快速點擊
我們可以用事件分發(fā)機(jī)制來防止快速點擊,如果對一個按鈕就行快速點擊那就會出現(xiàn)很糟糕的后果,所以一般的app中又要做防止快速點擊的操作,有些人對一些按鈕重復(fù)進(jìn)行快速點擊的操作,那就很浪費時間,可以直接在activity中做處理。
重寫activity的dispatchTouchEvent方法,記錄最后一次觸發(fā)點擊事件的事件,每次點擊都獲取時間,如果兩個時間相減小于XXX秒,就返回true,這樣快速點擊的時候事件就不會分發(fā)下去,大于這個時間就返回super。
在activity中寫
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if (System.currentTimeMillis() - lastTime < 500) {
return true;
}
}
return super.dispatchTouchEvent(ev);
}
當(dāng)然除了這樣子做之外,你也可以自己寫個根布局,然后所有xml中的布局都寫這個根布局,再用viewgroup的onInterceptTouchEvent來攔截也行。
2.仿dialog點擊外部內(nèi)容消失效果
我也是因為這個需求所以才想寫這個文章大,假如我要做一個圖層,實現(xiàn)在recyclerview的item中彈出的效果,對item的彈框效果的圖層,如果你是用一種圖層的思想你就知道這個圖層應(yīng)該是做在item上,activity下,所以無法使用dialog或popupwindow,因為這兩個彈框都是頂層的圖層,所以只能加一層布局來顯示和隱藏達(dá)到效果。而要實現(xiàn)這個效果,可以在activity的dispatchTouchEvent中加判斷。