以前沒太注意,很久沒用之后再使用發(fā)現(xiàn)有些地方模糊了例证,就是那種不知道是對是錯的感覺,然后又要重復(fù)上網(wǎng)去找資料迷捧,所以打算自己整理一篇织咧,有很多時(shí)候,一些特殊的需要要是能巧妙的運(yùn)用事件分發(fā)機(jī)制其實(shí)能很快的去解決問題漠秋。
一.流程
1.打印全流程
對于activity,viewgroup和view來說笙蒙,如果不再任意一個(gè)流程消費(fèi)事件,就會打印出這個(gè)結(jié)果庆锦。
這個(gè)就不用多解釋了捅位,如果都沒消費(fèi)事件,會在最后一句打印出ACTION_DOWN沒有被處理搂抒。
2.圖解過程
由上面打印的過程可以做出下面一張事件分發(fā)時(shí)的流程圖:
但是在activity艇搀,viewgroup和view的dispatchTouchEvent、onTouchEvent這些方法中求晶,返回值是一個(gè)布爾類型的焰雕,有三種情況,false誉帅,true和super淀散,分別對這三種情況和分發(fā)流程進(jìn)行探究后得到下圖:
圖太麻煩了,我就不重新畫了蚜锨,從網(wǎng)上找了一張档插,不同的是,圖中的onTouchEvent亚再,我試過郭膛,如果傳的是super,是會被消費(fèi)的氛悬,而不是返回上一層则剃。
3.一般情況下的事件分發(fā)
上面的情況我是重寫viewgroup重寫view去重寫onTouchEvent和dispatchTouchEvent方法,但是實(shí)際操作中不會總是這種情況如捅,因?yàn)槲覀儾豢赡馨呀佑|到事件分發(fā)需求的控件都重寫棍现,那樣就太麻煩了。所以先來看看一般情況下的分發(fā)情況镜遣。
我把自定義view換成普通的view然后寫onClick方法己肮,打印以下結(jié)果
發(fā)現(xiàn)在onClick事件中,view會消費(fèi)事件,即便你在onClick中沒做什么操作谎僻,事件也會被view給消費(fèi)娄柳,那不寫onClick方法呢?
發(fā)現(xiàn)打印結(jié)果中艘绍,即便沒對View監(jiān)聽赤拒,事件也不會往上傳,然后我打算看看view中的源碼
我* 诱鞠, 看來我本地是看不了了挎挖,別的地方找找。如果你看到了源碼航夺,你會發(fā)現(xiàn)臥槽真尼瑪多肋乍,一大堆判斷。我就找來了一個(gè)別人整理過的所寫版的敷存。感謝這位大神,很有良心堪伍。
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也就是被消費(fèi)。我看了源碼就知道了帝雇,當(dāng)然沒貼出來涮俄,這里設(shè)置setClickable的話就會有神奇的效果,我設(shè)置view.setClickable(false);
打印出下面的結(jié)果尸闸。
所以能得出一個(gè)簡單的結(jié)論彻亲,一般情況下你不從寫view,要讓這個(gè)view的事件往上層分發(fā)吮廉,需要設(shè)置setClickable(false)
那有的朋友說苞尝,我要有那種點(diǎn)擊view之后,view先做操作宦芦,然后viewgroup在做操作宙址,而且還不是自定義view和viewgrou的條件下。如果是直接對View設(shè)置onClickListener的話是無法達(dá)到這個(gè)效果的调卑,所以只能對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的點(diǎn)擊事件抡砂,再執(zhí)行viewgroup的點(diǎn)擊事件。
4.總結(jié)
事件分發(fā)有意思的地方就在于恬涧,你想讓什么去觸發(fā)這個(gè)事件注益,并且是否分發(fā)給上一層去做處理,更有意思的地方在于這個(gè)機(jī)制是先向下分發(fā),由activity分發(fā)給viewgroup再分發(fā)給view,之后執(zhí)行是向上執(zhí)行崭别,先由view執(zhí)行塘偎。所以它使用起來會很靈活琉历,比如說你想做一系列的點(diǎn)擊事件硼端,點(diǎn)擊一個(gè)按鈕后activity先執(zhí)行某步操作亚兄,view再執(zhí)行某步操作低滩,然后activity再執(zhí)行某部操作顽冶,這個(gè)做法也是可以做到的欺抗。
二.事件分發(fā)的靈活用法
事件分發(fā)他是一個(gè)機(jī)制,所以它可以適用于很多的場景强重,不要說它只能用于處理特殊的點(diǎn)擊事件绞呈,那可真是暴殄天物。
1.防止快速點(diǎn)擊
我們可以用事件分發(fā)機(jī)制來防止快速點(diǎn)擊间景,如果對一個(gè)按鈕就行快速點(diǎn)擊那就會出現(xiàn)很糟糕的后果佃声,所以一般的app中又要做防止快速點(diǎn)擊的操作,有些人對一些按鈕重復(fù)進(jìn)行快速點(diǎn)擊的操作倘要,那就很浪費(fèi)時(shí)間圾亏,可以直接在activity中做處理。
重寫activity的dispatchTouchEvent方法封拧,記錄最后一次觸發(fā)點(diǎn)擊事件的事件志鹃,每次點(diǎn)擊都獲取時(shí)間,如果兩個(gè)時(shí)間相減小于XXX秒泽西,就返回true曹铃,這樣快速點(diǎn)擊的時(shí)候事件就不會分發(fā)下去,大于這個(gè)時(shí)間就返回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)然除了這樣子做之外陕见,你也可以自己寫個(gè)根布局,然后所有xml中的布局都寫這個(gè)根布局味抖,再用viewgroup的onInterceptTouchEvent來攔截也行评甜。
2.仿dialog點(diǎn)擊外部內(nèi)容消失效果
我也是因?yàn)檫@個(gè)需求所以才想寫這個(gè)文章大,假如我要做一個(gè)圖層仔涩,實(shí)現(xiàn)在recyclerview的item中彈出的效果蜕着,對item的彈框效果的圖層,如果你是用一種圖層的思想你就知道這個(gè)圖層應(yīng)該是做在item上红柱,activity下承匣,所以無法使用dialog或popupwindow,因?yàn)檫@兩個(gè)彈框都是頂層的圖層锤悄,所以只能加一層布局來顯示和隱藏達(dá)到效果韧骗。而要實(shí)現(xiàn)這個(gè)效果,可以在activity的dispatchTouchEvent中加判斷零聚。