窺探Android Touch事件內(nèi)幕系列之二

上一篇文章我們主要介紹了Android UI事件處理機(jī)制-基于監(jiān)聽器方式赤惊、基于回調(diào)方法寝受,同時(shí)從View的角度分析了Touch事件分發(fā)流程妻熊。這一篇文章我們將從ViewGroup角度來分析Touch事件分發(fā)流程灶轰,之前計(jì)劃的關(guān)于Robolectric如何測(cè)試相關(guān)事件分發(fā)流程將會(huì)在后續(xù)『?jiǎn)卧獪y(cè)試之-自定義View』中進(jìn)行介紹羡洁。

事件傳遞流程

Touch事件在Android中對(duì)應(yīng)的就是MotionEvent類。對(duì)Touch事件的分發(fā)其實(shí)就是對(duì)MotionEvent對(duì)象進(jìn)行分發(fā)苛蒲。當(dāng)我們?cè)谄聊簧线M(jìn)行一次點(diǎn)擊操作時(shí)卤橄,MotionEvent就產(chǎn)生了,Android系統(tǒng)會(huì)將這個(gè)MotionEvent對(duì)象傳遞到一個(gè)具體的View進(jìn)行處理撤防,這個(gè)傳遞過程就是事件分發(fā)虽风。Android事件分發(fā)是一種委托思想:上層委托下層棒口,父容器委托子元素處理寄月。

下圖是Android系統(tǒng)的界面結(jié)構(gòu)圖:最頂層為Activity的ViewGroup辜膝,下面有若干的ViewGroup節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)之下又有若干的ViewGroup節(jié)點(diǎn)或者View節(jié)點(diǎn)漾肮,依次類推厂抖。當(dāng)一個(gè)Touch事件到達(dá)根節(jié)點(diǎn),即Acitivty的ViewGroup時(shí)克懊,該Touch事件會(huì)被依次分發(fā)忱辅,分發(fā)過程就是調(diào)用子View(ViewGroup)的dispatchTouchEvent方法實(shí)現(xiàn)的。簡(jiǎn)單來說谭溉,就是ViewGroup遍歷它包含著的子View墙懂,調(diào)用每個(gè)View的dispatchTouchEvent方法,而當(dāng)子View為ViewGroup時(shí)扮念,又會(huì)通過調(diào)用ViwGroup的dispatchTouchEvent方法繼續(xù)調(diào)用其內(nèi)部的View的dispatchTouchEvent方法损搬。下圖中Touch事件下發(fā)順序是這樣的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只負(fù)責(zé)事件的分發(fā)柜与,它擁有boolean類型的返回值巧勤,當(dāng)返回為true時(shí),順序下發(fā)會(huì)中斷弄匕。在上述例子中如果⑤的dispatchTouchEvent返回結(jié)果為true颅悉,那么⑥-⑦-③-④將都接收不到本次Touch事件。(這段節(jié)選自:Android:30分鐘弄明白Touch事件分發(fā)機(jī)制

Android View結(jié)構(gòu)模型

ViewGroup繼承自View迁匠,ViewGroup中對(duì)觸摸事件的處理剩瓶,很多也都繼承于View。但是城丧,ViewGroup又有自己對(duì)觸摸事件的特定處理延曙。ViewGroup重載了dispatchTouchEvent()方法,新增了onInterceptTouchEvent()方法芙贫。上一篇文章中我們已經(jīng)分析過View類的事件處理機(jī)制-dispatchTouchEvent搂鲫、onTouchEvent,本篇將會(huì)分析ViewGroup的dispatchTouchEvent分發(fā)流程磺平。

ViewGroup->dispatchTouchEvent流程

關(guān)于ViewGroup->dispatchTouchEvent已經(jīng)有很多大神們進(jìn)行了詳細(xì)的源碼分析魂仍,我這里給出一些鏈接,方便大家去參考

1拣挪、Android 觸摸事件機(jī)制(四) ViewGroup中觸摸事件詳解

2擦酌、 13.View的事件分發(fā)機(jī)制——dispatchTouchEvent詳解

3、Android ViewGroup事件分發(fā)機(jī)制

為節(jié)約篇幅這篇文章就不貼源碼了菠劝,將以精心烹制的『流程圖』作為?主菜赊舶,另配上香甜可口的甜點(diǎn)『解析』為大家呈現(xiàn)ViewGroup中dispatchTouchEvent這道饕餮盛宴。

ViewGroup->dispatchTouchEvent整體流程圖
  • Down操作:首先會(huì)通過攔截機(jī)制判斷是否需要攔截,如果攔截笼平,則不進(jìn)行子View的Down操作分發(fā)园骆,直接由當(dāng)前ViewGroup處理;反之則循環(huán)遍歷子View寓调,進(jìn)行事件分發(fā)锌唾。當(dāng)然循環(huán)遍歷所有子View之后也可能存在沒有子View處理該Down操作,這個(gè)時(shí)候會(huì)繼續(xù)交給當(dāng)前ViewGroup處理夺英。
  • Up晌涕、Move操作:也是先通過攔截機(jī)制判斷是否需要攔截,如果攔截痛悯,則由當(dāng)前ViewGroup直接處理余黎;反之則分發(fā)給處理Down操作的子View進(jìn)行處理。

流程圖中涉及到的攔截機(jī)制载萌、TouchTarget惧财、MotionEvent、Down操作分發(fā)流程等等炒考,會(huì)在下面的內(nèi)容中為大家一一講解可缚。

攔截機(jī)制

在自定義ViewGroup中,有時(shí)候需要實(shí)現(xiàn)Touch事件攔截斋枢,比如ListView下拉刷新就是典型的Touch事件攔截的例子帘靡。Touch事件攔截就是在Touch事件被父 view攔截,不會(huì)分發(fā)給其child瓤帚,即使觸摸發(fā)生在該child身上描姚。被攔截的事件會(huì)轉(zhuǎn)到parent view的onTouchEvent方法中進(jìn)行處理。

那么ViewGroup的攔截機(jī)制具體原則是什么呢戈次?我們先來看下流程圖轩勘,方便大家理解。

ViewGroup->dispatchTouchEvent->攔截機(jī)制流程圖.png

這里根據(jù)流程圖總結(jié)下具體原則:

  • ViewGroup有一個(gè)禁止攔截的標(biāo)志位:FLAG_DISALLOW_INTERCEPT怯邪,如果調(diào)用requestDisallowInterceptTouchEvent()绊寻,該標(biāo)志位為True,則禁止該ViewGroup攔截事件悬秉。
  • ViewGroup新增的接口onInterceptTouchEvent()澄步,默認(rèn)是不攔截的,即返回false和泌;如果你需要攔截村缸,只要return true就行了,這樣該事件就不會(huì)往子View傳遞了武氓。
  • Down操作-Touch事件的開始梯皿,此時(shí)ViewGroup會(huì)先根據(jù)FLAG_DISALLOW_INTERCEPT標(biāo)志位判斷仇箱,如果允許攔截,則進(jìn)一步調(diào)用新增接口onInterceptTouchEvent()來確定Down操作是否繼續(xù)傳遞給子View东羹。
  • Move剂桥、UP操作-如果mFirstTouchTarget != null(Down操作已經(jīng)被某個(gè)子View消費(fèi)掉了),此時(shí)百姓,才有必要再進(jìn)一步判斷當(dāng)前ViewGroup是否需要對(duì)Move渊额、UP進(jìn)行攔截况木,具體如何判斷同Down操作垒拢。
  • Move、UP操作-如果mFirstTouchTarget == null(Down操作已經(jīng)被當(dāng)前ViewGroup攔截了火惊,或者遍歷了所有子View 但都沒有對(duì)Down操作進(jìn)行處理)求类,此時(shí),完全沒必要進(jìn)一步判斷當(dāng)前ViewGroup是否要攔截屹耐,因?yàn)檫@種情況Down操作肯定已經(jīng)由該ViewGroup了尸疆,后續(xù)的Move、UP自然也由該ViewGroup處理惶岭。

TouchTarget

TouchTarget是ViewGroup的一個(gè)內(nèi)部類寿弱,是一個(gè)觸摸對(duì)象的鏈表類。ViewGroup類中mFirstTouchTarget就是當(dāng)前ViewGroup中觸摸對(duì)象鏈表的頭節(jié)點(diǎn)按灶,用于記錄處理某Down操作的所有子View和觸摸點(diǎn)(對(duì)于多點(diǎn)觸摸症革,需要記錄每次的觸摸點(diǎn))信息。下面給出了這個(gè)類的源碼鸯旁,方便大家理解噪矛。

 /* Describes a touched view and the ids of the pointers that it has captured.
     *
     * This code assumes that pointer ids are always in the range 0..31 such that
     * it can use a bitfield to track which pointer ids are present.
     * As it happens, the lower layers of the input dispatch pipeline also use the
     * same trick so the assumption should be safe here...
     */
    private static final class TouchTarget {
        private static final int MAX_RECYCLED = 32;
        private static final Object sRecycleLock = new Object[0];
        private static TouchTarget sRecycleBin;
        private static int sRecycledCount;

        public static final int ALL_POINTER_IDS = -1; // all ones

        // The touched child view.
        public View child;

        // The combined bit mask of pointer ids for all pointers captured by the target.
        public int pointerIdBits;

        // The next target in the target list.
        public TouchTarget next;

        private TouchTarget() {
        }

        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                     sRecycledCount--;
                    target.next = null;
                }
            }
            target.child = child;
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() {
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
                child = null;
            }
        }
    }

MotionEvent

這篇文章一開始就介紹了MotionEvent是事件源(Button、CheckBox铺罢、EditText等)產(chǎn)生的Touch事件艇挨。我們需要從MotionEvent中獲取哪些信息呢?

  • 事件類型

    • 常見的事件類型有: ACTION_DOWN: 表示用戶開始觸摸韭赘、 ACTION_MOVE: 表示用戶手指移動(dòng)缩滨、ACTION_UP:表示用戶抬起了手指 、ACTION_POINTER_DOWN:有一個(gè)非主要的手指按下泉瞻、ACTION_POINTER_UP:一個(gè)非主要的手指抬起來脉漏。后兩者是在Android 2.2支持多點(diǎn)觸摸之后增加的事件類型。
    • 獲取事件類型的方法:getActionMasked()
  • 事件觸摸點(diǎn)索引信息

    • Android是支持多點(diǎn)觸控瓦灶,通過觸摸點(diǎn)索引信息可以得知一個(gè)MotionEvent事件類型是哪個(gè)觸摸點(diǎn)觸發(fā)鸠删。
    • 獲取觸摸點(diǎn)索引信息的方法:getActionIndex()
  • 事件發(fā)生的位置信息

    • getX()方法獲得事件發(fā)生時(shí),觸摸的中間區(qū)域在屏幕的X軸
    • getY() 獲得事件發(fā)生時(shí),觸摸的中間區(qū)域在屏幕的Y軸
    • 多點(diǎn)觸摸還可以通過getX(int pointerIndex) 和 getY(int pointerIndex)來獲取對(duì)應(yīng)手指事件的X、Y軸信息
    • 事件發(fā)生的位置信息的坐標(biāo)系是Android系統(tǒng)坐標(biāo)系(這個(gè)概念可以參考文章:Android中的坐標(biāo)系以及獲取坐標(biāo)的方法

Down操作處理流程

當(dāng)Touch事件MotionEvent中的ACTION_DOWN贼陶、ACTION_POINTER_DOWN操作來臨時(shí)ViewGroup的分發(fā)流程是如何的呢刃泡?請(qǐng)先看流程圖巧娱,我們?cè)賮矸治觥?/p>

ViewGroup->dispatchTouchEvent->Down操作處理流程.png
  • 首先我們需要判斷當(dāng)前事件是否是取消事件、是否已經(jīng)被ViewGroup攔截烘贴,如果是取消事件或者已經(jīng)被攔截禁添,那么該Down操作是沒有必要在子View中進(jìn)行事件分發(fā),則直接跳出該流程桨踪。
  • 核心是循環(huán)遍歷當(dāng)前ViewGroup的所有子View老翘。
  • 循環(huán)過程中第一步是判斷子View是否可接受Touch事件,同時(shí)當(dāng)前的Touch事件的位置是否位于子View中锻离。如果滿足這兩個(gè)條件才會(huì)進(jìn)一步對(duì)該子View和Touch事件進(jìn)行關(guān)聯(lián)铺峭、將Touch事件分發(fā)給該子View;如果不滿足這兩個(gè)條件汽纠,則說明當(dāng)前Touch事件和該子View沒有任何聯(lián)系卫键,直接退出當(dāng)前循環(huán),進(jìn)行下一個(gè)子View的處理虱朵。
  • 如何將子View和當(dāng)前Touch事件進(jìn)行關(guān)聯(lián)呢莉炉?TouchTarget鏈表粉墨登場(chǎng)(噔噔噔。碴犬。絮宁。)通過mFirstTouchTarget鏈表中獲取和當(dāng)前子View相關(guān)的TouchTarget,如果已經(jīng)存在該子View相關(guān)的TouchTarget服协,直接更新該TouchTarget的pointerIdBits屬性绍昂,讓其包含當(dāng)前觸摸點(diǎn)信息,同時(shí)退出循環(huán)遍歷子View的流程蚯涮。(例如:第一個(gè)手指觸摸在View - A上治专,這個(gè)時(shí)候第二個(gè)手指也觸摸在View - A上)如果不存在該子View相關(guān)的TouchTarget,則新建一個(gè)并插入到mFirstTouchTarget鏈表中遭顶,同時(shí)調(diào)用該子View 的dispatchTouchTarget方法张峰,這里的流程就回歸到我們上一篇中關(guān)于View的事件處理流程。根據(jù)子View的dispatchTouchTarget返回值判斷子View是否消費(fèi)了當(dāng)前Touch事件棒旗,若消費(fèi)則退出循環(huán)遍歷子View的流程喘批,反之則繼續(xù)遍歷。

Up铣揉、Move操作處理流程

ViewGroup主要是先通過對(duì)Down操作進(jìn)行分發(fā)饶深,記錄處理Down操作的子View鏈表,然后循環(huán)遍歷該鏈表逛拱,完成Up敌厘、Move操作的分發(fā)處理。

ViewGroup->dispatchTouchEvent->Up/Move操作處理流程

總結(jié)

  • ViewGroup的dispatchTouchEvent負(fù)責(zé)事件分發(fā)朽合,由上至下俱两,有父容器至子View依次分發(fā)
  • 如果ViewGroup找到了能夠處理該事件的子View饱狂,則直接交給子View處理,自己的onTouchEvent不會(huì)被觸發(fā)
  • 可以通過復(fù)寫onInterceptTouchEvent(ev)方法宪彩,攔截子View的事件(即return true)休讳,把事件交給自己處理,則會(huì)執(zhí)行自己對(duì)應(yīng)的onTouchEvent方法
  • 子View可以通過調(diào)用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup對(duì)其事件的攔截
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尿孔,一起剝皮案震驚了整個(gè)濱河市俊柔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌活合,老刑警劉巖雏婶,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芜辕,居然都是意外死亡尚骄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門侵续,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憨闰,你說我怎么就攤上這事状蜗。” “怎么了鹉动?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵轧坎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我泽示,道長(zhǎng)缸血,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任械筛,我火速辦了婚禮捎泻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘埋哟。我一直安慰自己笆豁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布赤赊。 她就那樣靜靜地躺著闯狱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抛计。 梳的紋絲不亂的頭發(fā)上哄孤,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音吹截,去河邊找鬼瘦陈。 笑死朦肘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的双饥。 我是一名探鬼主播媒抠,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咏花!你這毒婦竟也來了趴生?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤昏翰,失蹤者是張志新(化名)和其女友劉穎苍匆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棚菊,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浸踩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了统求。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检碗。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖码邻,靈堂內(nèi)的尸體忽然破棺而出折剃,到底是詐尸還是另有隱情,我是刑警寧澤像屋,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布怕犁,位于F島的核電站,受9級(jí)特大地震影響己莺,放射性物質(zhì)發(fā)生泄漏奏甫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一凌受、第九天 我趴在偏房一處隱蔽的房頂上張望阵子。 院中可真熱鬧,春花似錦胁艰、人聲如沸款筑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈梳。三九已至,卻和暖如春解虱,著一層夾襖步出監(jiān)牢的瞬間攘须,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工殴泰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留于宙,地道東北人浮驳。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捞魁,于是被迫代替她去往敵國(guó)和親至会。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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