完全理解Android TouchEvent事件分發(fā)機(jī)制(一)

本文原地址:https://blog.csdn.net/zhcswlp0625/article/details/68930797
也是我寫(xiě)的,但是沒(méi)有轉(zhuǎn)移過(guò)來(lái),尷尬着倾。(本人放棄csdn分享戒职,轉(zhuǎn)向簡(jiǎn)書(shū))。

下篇文章地址
完全理解Android TouchEvent事件分發(fā)機(jī)制(二)

本文能給你帶來(lái)和解決一些你模糊的Touch事件概念及用法

  • 1.掌握View及ViewGroup的TouchEvent事件分發(fā)機(jī)制
  • 2.為解決View滑動(dòng)沖突及點(diǎn)擊事件消費(fèi)提供支持
  • 3.為你解決面試中的一些問(wèn)題巨双。

Touch事件分發(fā)中只有兩個(gè)主角:ViewGroup和View。

Activity的Touch事件事實(shí)上是調(diào)用它內(nèi)部的ViewGroup的Touch事件霉祸,可以直接當(dāng)成ViewGroup處理筑累。

Activity、ViewGroup丝蹭、View都關(guān)心Touch事件慢宗,其中ViewGroup的關(guān)心的事件有三個(gè):onInterceptTouchEvent、dispatchTouchEvent奔穿、onTouchEvent镜沽。

Activity和View關(guān)心的事件只有兩個(gè):dispatchTouchEvent、onTouchEvent贱田。

只有ViewGroup可以對(duì)事件進(jìn)行攔截缅茉。

在Android中Touch觸摸事件主要包括點(diǎn)擊(onClick)、長(zhǎng)按(onLongClick)湘换、拖拽(onDrag)宾舅、滑動(dòng)(onScroll)等,

其中Touch的第一個(gè)狀態(tài)是 ACTION_DOWN彩倚,表示按下了屏幕后筹我,touch將會(huì)有后續(xù)事件,比如移動(dòng)帆离、抬起等蔬蕊。

一個(gè)Action_DOWN,一個(gè)ACTION_UP哥谷,許多個(gè)ACTION_MOVE岸夯,構(gòu)成了Android中眾多的Touch交互事件。

安卓里經(jīng)常會(huì)有多個(gè)布局嵌套们妥,View重疊猜扮,View的Visibility設(shè)置等等,還有ViewGroup包含View的情況监婶。
這個(gè)時(shí)候點(diǎn)擊到子View時(shí)旅赢,其實(shí)也是同時(shí)點(diǎn)到ViewGroup這個(gè)父控件的齿桃,那是把這個(gè)點(diǎn)擊事件應(yīng)該是怎么分發(fā)的呢(有沒(méi)有遇到過(guò)listview或recyclerview的item事件或者是item中的控件是不是沒(méi)反應(yīng)撒)?

觸摸事件分發(fā)機(jī)制涉及的三個(gè)重要方法:

 public boolean dispatchTouchEvent(MotionEvent event)

dispatchTouchEvent用來(lái)進(jìn)行事件的分發(fā)煮盼。如果事件能夠傳遞給當(dāng)前的View短纵,那么此方法一定會(huì)被調(diào)用,
返回結(jié)果受當(dāng)前View或者是ViewGroup的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響僵控,表示是否消耗當(dāng)前事件香到。

public boolean onInterceptTouchEvent(MotionEvent event)

onInterceptTouchEvent是ViewGroup提供的方法,用來(lái)判斷是否攔截某個(gè)事件报破,如果當(dāng)前View攔截了某個(gè)事件悠就,
那么在同一個(gè)事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用充易,返回結(jié)果表示是否攔截當(dāng)前事件理卑。默認(rèn)返回false,返回true表示攔截蔽氨。

  public boolean onTouchEvent(MotionEvent event)

onTouchEvent在dispatchTouchEvent方法中調(diào)用,用來(lái)處理點(diǎn)擊事件帆疟,返回結(jié)果表示是否消耗當(dāng)前的事件鹉究,如果不消耗,
則在同一個(gè)事件序列中踪宠,當(dāng)前View無(wú)法再次接受到事件自赔。view中默認(rèn)返回true,表示消費(fèi)了這個(gè)事件柳琢。

今天所使用的Demo目錄結(jié)構(gòu)及Activity如圖所示:

[外鏈圖片轉(zhuǎn)存失敗(img-dxeeN54q-1566869743609)(http://oc5bjv3gr.bkt.clouddn.com/touchdetailsesdgdf.png)]

首先我們來(lái)看一下dispatchTouchEvent(MotionEvent event)

布局activity_touch_test.xml

<?xml version="1.0" encoding="utf-8"?>
<com.shanlovana.rcimageview.touchviews.GrandPaViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_touch_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.shanlovana.rcimageview.TouchTestActivity">

    <com.shanlovana.rcimageview.touchviews.FatherViewGroup
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="match_parent">

        <com.shanlovana.rcimageview.touchviews.LogImageView
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:src="@drawable/damimi"/>


    </com.shanlovana.rcimageview.touchviews.FatherViewGroup>


</com.shanlovana.rcimageview.touchviews.GrandPaViewGroup>

下面是三層布局及預(yù)覽情況:

[外鏈圖片轉(zhuǎn)存失敗(img-RjazP8Pr-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/sancengbujujiyulan.png)]

點(diǎn)擊一下圖片:看一下打由芊痢:

03-31 09:02:40.554 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  dispatchTouchEvent  Event 0
03-31 09:02:40.556 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  onTouchEvent  Event 0
03-31 09:02:40.558 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 09:02:40.559 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onTouchEvent  Event 0

源碼中0,1柬脸,2他去,3,4所代表的Action

   public static final int ACTION_DOWN = 0;
    

    public static final int ACTION_UP   = 1;
    

    public static final int ACTION_MOVE  = 2;
    

    public static final int ACTION_CANCEL = 3;
    

    public static final int ACTION_OUTSIDE = 4;

為什么是這樣一個(gè)從父級(jí)到子級(jí)再到父級(jí)的順序呢倒堕?

來(lái)灾测,follow me進(jìn)入源碼查看,所有的核心在于ViewGroup的dispatchTouchEvent方法:

boolean dispatchTouchEvent() {
    // 是否攔截
     final boolean intercepted;
    intercepted = onInterceptTouchEvent(ev);
    
    // final boolean canceled = resetCancelNextUpFlag(this)
                 //   || actionMasked == MotionEvent.ACTION_CANCEL;
    if( !intercepted) {
        // 如果不攔截遍歷所有child垦巴,判斷是否有分發(fā)
        boolean handled;
        if (child == null) {
            // 等同于handled = onTouchEvent()
            handled = super.dispatchTouchEvent();
        } else {
            // 如果有child媳搪,再調(diào)用child的分發(fā)方法
            handled = child.dispatchTouchEvent();
        }

        if(handled) {
            touchTarget = child;
            break;
        }   
    }

    if(touchTarget == null) {
        // 如果所有child中都沒(méi)有消費(fèi)掉事件
        // 那么就把自己作為沒(méi)child的普通View
        handled = super.dispatchTouchEvent();
    }

    return handled;
}

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

dispatchTouchEvent方法的作用是將屏幕點(diǎn)擊事件進(jìn)行向下分發(fā)(子一級(jí))傳遞到目標(biāo)控件上,或者傳遞給自己骤宣。

如果事件被(自己或者下面某一層的子控件)處理掉了的話秦爆,就返回true,否則返回false

那問(wèn)題來(lái)了憔披,如果我沒(méi)有child了等限,或者我就是一個(gè)View,那我的dispatchTouchEvent返回值要如何獲取呢?

這種情況下就會(huì)使用父類的dispatchTouchEvent方法精刷,
也就是調(diào)用View類中的實(shí)現(xiàn)拗胜,簡(jiǎn)化代碼如下:

boolean dispatchTouchEvent() {
    // 實(shí)質(zhì)上就是調(diào)用onTouchEvent用其返回值
    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;
    }
    return result;
}

由此可見(jiàn),只要是enable=false或者沒(méi)有設(shè)置過(guò)touchListener, 那么他一定會(huì)調(diào)用onTouchEvent怒允,且dispatchTouchEvent的返回值就是onTouchEvent的返回值埂软。

ViewGroup進(jìn)行事件的分發(fā),一直到自己或者是最底層的View纫事,邏輯圖如下勘畔。

[外鏈圖片轉(zhuǎn)存失敗(img-K0iwEqb4-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/luojiouxiangtu.png)]

現(xiàn)在我們基本知道了事件的分發(fā)dispatchTouchEvent,最終調(diào)用了onTouchEvent方法

接著我們來(lái)理解和講解onInterceptTouchEvent攔截方法

該方法用于攔截事件向下分發(fā)

當(dāng)返回值為true時(shí)丽惶,就會(huì)攔截TouchEvent不再向下傳遞炫七,直接交給自己的onTouchEvent方法處理。返回false則不攔截钾唬。

Demo中的Parent層的onInterceptTouchEvent返回值改為true万哪。

運(yùn)行一下,點(diǎn)View抡秆,看下輸出結(jié)果:

03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 11:45:39.954 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 11:45:39.955 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onTouchEvent  Event 0

即當(dāng)事件一層層向下傳遞到parent時(shí)奕巍,被他就攔截了下來(lái)然后自己消費(fèi)使用。

intercepted為true,沒(méi)有進(jìn)入FatherViewGroup的條件儒士,就跳過(guò)了child.dispatchTouchEvent的向下事件分發(fā)(結(jié)合我的demo看比較直觀)的止。

最后我們來(lái)講解 onTouchEvent方法

方法的主體內(nèi)容其實(shí)是處理具體操作邏輯的,是產(chǎn)生一次點(diǎn)擊還是一次橫縱向的滑動(dòng)等

而他的返回值才會(huì)影響整個(gè)事件分發(fā)機(jī)制着撩,
意義在于通知父級(jí)的ViewGroup們是否已經(jīng)消費(fèi)找到目標(biāo)Target了诅福。

把示例中的Parent的TouchEvent返回值改為true。攔截方法不變
點(diǎn)一下View(小伙子拖叙,如果你不是點(diǎn)一下氓润,會(huì)出現(xiàn)不同的結(jié)果哦),則輸出日志為:

03-31 12:01:21.661 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 12:01:21.674 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 12:01:21.676 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  dispatchTouchEvent  Event 0
03-31 12:01:21.678 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  onTouchEvent  Event 0
03-31 12:01:21.681 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 1

先看Down的邏輯憋沿,對(duì)應(yīng)的源碼執(zhí)行順序如下

Father調(diào)用super.dispatchTouchEvent實(shí)際上是調(diào)用了onTouchEvent方法旺芽,

這里因?yàn)槲覀冃薷某闪藅rue,所以dispatchTouchEvent最終也返回true辐啄。

所以返回到GrandPa中采章,touchTarget 就非空了,

因此GrandPa的onTouchEvent也沒(méi)有執(zhí)行~

可以看出來(lái),事件一旦被某一層消費(fèi)掉壶辜,其它層就不會(huì)再消費(fèi)了

到這里其實(shí)對(duì)事件分發(fā)的機(jī)制就有個(gè)大概了解了看了源碼也知道里面的原理是怎么回事悯舟。

突然有事,明天接著更新砸民。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抵怎,一起剝皮案震驚了整個(gè)濱河市奋救,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌反惕,老刑警劉巖尝艘,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異姿染,居然都是意外死亡背亥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門悬赏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狡汉,“玉大人,你說(shuō)我怎么就攤上這事闽颇《艽鳎” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵兵多,是天一觀的道長(zhǎng)尖啡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)剩膘,這世上最難降的妖魔是什么可婶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮援雇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椎扬。我一直安慰自己惫搏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布蚕涤。 她就那樣靜靜地躺著筐赔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揖铜。 梳的紋絲不亂的頭發(fā)上茴丰,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音天吓,去河邊找鬼贿肩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛龄寞,可吹牛的內(nèi)容都是我干的汰规。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼物邑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼溜哮!你這毒婦竟也來(lái)了滔金?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茂嗓,失蹤者是張志新(化名)和其女友劉穎餐茵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體述吸,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忿族,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刚梭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肠阱。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖朴读,靈堂內(nèi)的尸體忽然破棺而出屹徘,到底是詐尸還是另有隱情,我是刑警寧澤衅金,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布噪伊,位于F島的核電站,受9級(jí)特大地震影響氮唯,放射性物質(zhì)發(fā)生泄漏鉴吹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一惩琉、第九天 我趴在偏房一處隱蔽的房頂上張望豆励。 院中可真熱鬧,春花似錦瞒渠、人聲如沸良蒸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嫩痰。三九已至,卻和暖如春窍箍,著一層夾襖步出監(jiān)牢的瞬間串纺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工椰棘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纺棺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓邪狞,卻偏偏與公主長(zhǎng)得像五辽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子外恕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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