事件分發(fā)機(jī)制

為什么會有事件分發(fā)機(jī)制妒蔚?

Android上的View是樹形結(jié)構(gòu)的,有可能重疊在一起月弛,當(dāng)我們點(diǎn)擊的地方有多個View都可以響應(yīng)的時候肴盏,這個點(diǎn)擊事件應(yīng)該給誰呢?為了解決這一問題就有了事件分發(fā)機(jī)制帽衙。

相關(guān)方法

  • dispatchTouchEvent() : 分發(fā)事件
  • onInterceptTouchEvent():判斷是否攔截事件
  • onTouchEvent() :處理事件

Activity和View(不能包含子View)是沒有onInterceptTouchEvent()的菜皂。Activity是事件最初的接收者,如果一開始就被它攔截了事件分發(fā)也就沒有了意義厉萝。View是沒有子View的恍飘,所以沒有攔截事件的方法。

源碼分析

1.當(dāng)點(diǎn)擊事件發(fā)生時谴垫,首先調(diào)用的是Activity的dispatchTouchEvent()方法章母,看一下該方法的實(shí)現(xiàn):

調(diào)用過程:如果是按下事件就調(diào)用onUserInteraction()方法,改方法是一個空方法翩剪,沒有任何實(shí)現(xiàn)乳怎,然后調(diào)用Window的superDispatchTouchEvent(ev)方法,如果該方法返回true,調(diào)用結(jié)束前弯;如果返回false蚪缀,就調(diào)用Activity的onTouchEvent()方法,自己處理點(diǎn)擊事件博杖。該方法比較簡單椿胯。

2.getWindow()返回的是Window(抽象類)的實(shí)現(xiàn)類PhoneWindow的對象,進(jìn)而我們找到PhoneWindow的superDispatchTouchEvent(ev)方法剃根。

在該方法中又調(diào)用mDecor的superDispatchTouchEvent(ev)方法哩盲。進(jìn)而我們找到mDecor

在該方法中調(diào)用父類的dispatchTouchEvent(ev)方法,DecorView 繼承FrameLayout,而FrameLayout又繼承ViewGroup廉油,所以我們找到ViewGroup的dispatchTouchEvent(ev)方法

到此處我們先小小總結(jié)一下

在Activity的dispatchTouchEvent()方法中會調(diào)用getWindow().superDispatchTouchEvent(ev),其實(shí)就是調(diào)用ViewGroup的dispatchTouchEvent(ev)方法惠险。

3.繼續(xù)查看ViewGroup的dispatchTouchEvent(ev)方法。該方法的代碼比較復(fù)雜抒线,我們只分析它的核心部分:

    ........

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

      ..................

if (!canceled && !intercepted) {
      ...........
}

在ViewGroup的dispatchTouchEvent(tv)方法中會調(diào)用onInterceptTouchEvent(ev)方法班巩,如果intercepted的值為true,也就是攔截了該事件嘶炭。就不會執(zhí)行 if (!canceled && !intercepted) 中的代碼,該區(qū)域的代碼主要是遍歷所有的子View眨猎,查看是否攔截與處理。

if (mFirstTouchTarget == null) {
     // No touch targets so treat this as an ordinary view.
     handled = dispatchTransformedTouchEvent(ev, canceled, null,
     TouchTarget.ALL_POINTER_IDS);
} 

如果是第一次觸摸就會執(zhí)行dispatchTransformedTouchEvent方法睡陪,在剛開始的時候mFirstTouchTarget肯定為空。繼而查看dispatchTransformedTouchEvent方法兰迫。該方法有四個參數(shù)信殊,其中第三個參數(shù)為child汁果,傳遞的值為null。

 if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event);
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
  } else {
       transformedEvent = event.split(newPointerIdBits);
  }

因?yàn)樯厦鎮(zhèn)鬟f的child為null须鼎,所有調(diào)用 handled = super.dispatchTouchEvent(event);就只執(zhí)行View的dispatchTouchEvent(ev)方法,該方法比較簡單晋控。

 ............

 if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
  }

 ...............

在該方法中會判斷,如果mOnTouchListener != null就會調(diào)用mOnTouchListener.onTouch(),并且返回true赡译,下面的onTouchEvent(ev)也就不會再執(zhí)行,這也是為什么給View設(shè)置OnTouchListener后不再調(diào)用onTouch方法蝌焚。也可以說如果設(shè)置了OnTouchListener,那么View的onTouchEvent(ev)方法就不會再執(zhí)行只洒。

綜上所述:如果上層View不攔截许帐,事件就會一直傳遞到View中毕谴,調(diào)用View的dispatchTouchEvent(ev)方法成畦,如果設(shè)置了OnTouchListener距芬,View的onTouchEvent(ev)方法就不會執(zhí)行了,事件就此消耗循帐,也不會回傳給父類了框仔。如果沒有設(shè)置OnTouchListener,就會調(diào)用View的onTouchEvent(ev)方法拄养。

你應(yīng)該知道的

通過對事件分發(fā)機(jī)制源碼的閱讀和了解离斩,你應(yīng)該知道的:

  • 1.在ViewGroup中重寫onInterceptTouchEvent方法返回true為什么會攔截事件,并且該ViewGroup會消費(fèi)了Event(調(diào)用onOnTouchEvent)瘪匿。
  • 2.為什么事件不再向子控件繼續(xù)傳遞跛梗?
  • 3.當(dāng)父控件沒有攔截事件時,事件是如何傳遞到子控件的
  • 4.點(diǎn)擊事件中的x,y坐標(biāo)值都是以父布局的相對坐標(biāo)柿顶,這里又是如何一層一層轉(zhuǎn)換的
  • 5.為什么給自定義View設(shè)置OnTouchListener后不再調(diào)用 重寫的onTouch方法

總結(jié)

Android的時間分發(fā)機(jī)制還是比較復(fù)雜的茄袖,尤其是ViewGroup的dispatchTouchEvent(ev)方法,我們可以通過閱讀源碼來了解它的實(shí)現(xiàn)原理嘁锯。其實(shí)在項(xiàng)目開發(fā)中我們不必要非常清楚它的實(shí)現(xiàn)原理,也能解決時間沖突問題聂薪。只需要用好相關(guān)的方法即可( dispatchTouchEvent() 家乘,onInterceptTouchEvent(),onTouchEvent() )但是如果能清楚了了解它的實(shí)現(xiàn)原理藏澳,那么在遇到時間沖突問題時仁锯,更容易解決。拋開事件分發(fā)本身而言翔悠,它的代碼設(shè)計(jì)也值得我們學(xué)習(xí)滤港,這里面用到了責(zé)任鏈模式健田,雖然它們沒有繼承共同的類,但是它們都有共同的方法(dispatchTouchEvent)。其實(shí)通過源碼分析我們只僅僅能解決問題锐借,還能學(xué)習(xí)到更多的東西。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棋电,一起剝皮案震驚了整個濱河市栗涂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丹拯,老刑警劉巖站超,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異乖酬,居然都是意外死亡死相,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門咬像,熙熙樓的掌柜王于貴愁眉苦臉地迎上來算撮,“玉大人,你說我怎么就攤上這事钮惠。” “怎么了素挽?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長预明。 經(jīng)常有香客問我,道長酥馍,這世上最難降的妖魔是什么阅酪? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮术辐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘必孤。我一直安慰自己瑞躺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布赡勘。 她就那樣靜靜地躺著嘱么,像睡著了一般。 火紅的嫁衣襯著肌膚如雪几迄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天映胁,我揣著相機(jī)與錄音解孙,去河邊找鬼坑填。 笑死脐瑰,一個胖子當(dāng)著我的面吹牛廷臼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荠商,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莱没,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饰躲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眠寿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盒发,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拼卵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年蛮艰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片即寡。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡袜刷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墩蔓,到底是詐尸還是另有隱情梢莽,我是刑警寧澤昏名,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布阵面,位于F島的核電站,受9級特大地震影響嗽交,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夫壁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一盒让、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邑茄,春花似錦俊啼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彤路。三九已至芥映,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奈偏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工姆吭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唁盏,地道東北人检眯。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓锰瘸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親避凝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容