Android觸摸事件解析(自定義DrawerLayout)

基礎(chǔ)知識概述:

首先來了解三個方法:

  • dispatchTouchEvent(MotionEvent ev) 功能:事件的分發(fā)
  • onInterceptTouchEvent(MotionEvent ev) 功能:事件的攔截
  • onTouchEvent(MotionEvent ev) 功能:事件的消費
    可以看到這三個方法里面都包含了同一個類:MotionEvent 類橡伞,這個類里面包含了很多的代表觸摸動作的常量制轰,比如:
    • MotionEvent.ACTION_DOWN: 代表手指按下的動作
    • MotionEvent.ACTION_MOVE: 代表手指滑動的動作
    • MotionEvent.ACTION_CANCEL: 代表手指動作取消的動作
    • MotionEvent.ACTION_UP: 代表手指動作抬起的動作
      這里我們也是圍繞著這四個動作進行解析缆八,因為:** 從手指觸摸屏幕潜秋,到手指離開屏 幕蛔琅,會經(jīng)歷一次 ACTION_DOWN、若干次ACTION_MOVE或ACTION_CANCEL峻呛、和一次ACTION_UP罗售,這也被稱為一個事件序列。**

之后我們在說一下能夠觸發(fā)這三個方法傳遞觸摸事件的容器:

  • Activity 事件就是從它開始往下分發(fā)的钩述,這個容器里包含了dispatchTouchEvent()和onTouchEvent(),也就是擁有分發(fā)事件和消費事件的能力寨躁。
  • ViewGroup 它接收Activity的事件分發(fā),并且可以選擇是否繼續(xù)往下傳遞還是自己直接進行消費牙勘,所以它包含了上面三個方法职恳,具有事件的分發(fā)、攔截方面、消費功能放钦。
  • View 如果它的父容器不進行攔截的話,它接受父容器的事件分發(fā)恭金,并且判斷是否消費事件操禀,并且要把結(jié)果再通過dispatchTouchEvent()方法返回給父容器。
    用表格的形式展現(xiàn)下容器和方法之間的關(guān)系:

事件相關(guān)方法 | 方法功能 | Activity | ViewGroup | View
---||||---|||---
public boolean dispatchTouchEvent | 事件分發(fā) | Yes | Yes | Yes
public boolean onInterceptTouchEvent | 事件攔截 | No | Yes | No
public boolean onTouchEvent | 事件消費 | Yes | Yes | Yes

  • 分發(fā) : dispatchTouchEvent如果返回true横腿,則表示在當(dāng)前View或者其子View(子子...View)中颓屑,找到了處理事件的View斤寂;反之,則表示沒有尋找到揪惦。
  • 攔截: onInterceptTouchEvent如果返回true遍搞,則表示這個事件由當(dāng)前View進行處理,不管處理結(jié)果如何丹擎,都不會再向子View傳遞這個事件尾抑;反之,則表示當(dāng)前View不主動處理這個事件蒂培,除非他的子View返回的事件分發(fā)結(jié)果為false再愈。
  • 消費: onTouchEvent如果返回true,則表示當(dāng)前View消費了該事件护戳;反之翎冲,則表示當(dāng)前View沒有消費該事件,返回到父控件處理(如果有父控件的話)

代碼示例

需求:我想用DrawerLayou類來做滑動側(cè)邊看媳荒,但是有一個問題抗悍,就是當(dāng)側(cè)邊欄打開的時候,主屏幕會變暗并且不能觸發(fā)點擊事件钳枕,我現(xiàn)在想通過自定義的形式解決這一問題缴渊,并且當(dāng)側(cè)邊欄打開的時候點擊主界面的時候側(cè)邊欄不會自定收起。

先看下效果圖:


圖片.png

再看下布局的資源文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:titleTextColor="@color/colorAccent" />
    
    <com.mllwf.slidesidebar.TEDrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.mllwf.slidesidebar.CustormFrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <com.mllwf.slidesidebar.CustormButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                android:onClick="toCanSlieDrawer"
                android:padding="16dp"
                android:text="是否能滑動" />
        </com.mllwf.slidesidebar.CustormFrameLayout>

        <RelativeLayout
            android:id="@+id/nav_view"
            android:layout_width="150dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="@android:color/darker_gray">

            <Button
                android:id="@+id/btn_one"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimary"
                android:text="這是頂部按鈕"
                android:textColor="@color/colorAccent" />

            <Button
                android:id="@+id/btn_two"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:background="@color/colorPrimary"
                android:text="這是中間的按鈕"
                android:textColor="@color/colorAccent" />

            <Button
                android:id="@+id/btn_three"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:background="@color/colorPrimary"
                android:text="這是底部按鈕"
                android:textColor="@color/colorAccent" />
        </RelativeLayout>
    </com.mllwf.slidesidebar.TEDrawerLayout>
</LinearLayout>

簡要說明(請結(jié)合效果圖和布局文件):

  • 為了能讓側(cè)邊欄在標(biāo)題欄的下面鱼炒,我在DrawerLayout布局放在了ToolBar布局的下面衔沼,而根布局我用的是線性布局。
  • 可以看的我自定義了DrawerLayout類昔瞧,這樣做為了重寫上面三個觸摸事件分發(fā)指蚁,并且方便打印Log日志
  • DrawerLayout容器里面的第一個布局代表的是主界面布局,這里我用的是自定義的FrameLayout這樣是也是為了重寫事件分發(fā)和打印日志(注意重寫方法為了實現(xiàn)上面說的功能自晰,和打印Log)
  • 在主界面布局里面我放了一個自定義的按鈕這只是為了方便打印日志用凝化。
  • DrawerLayout容器里面第二個布局代表的是側(cè)邊欄布局。

下面分別看下分別看些這幾個自定義布局的相關(guān)代碼:

  • MainActivity.class(Activity)


    Activity觸摸事件方法.png
  • CustormFrameLayout(ViewGroup)


    ViewGroup觸摸事件.png
  • CustormButton(View)


    View事件觸摸方法.png

首先默認沒有打開側(cè)邊欄酬荞,點擊按鈕查看日志信息:


圖片.png

從上面的日志信息可以看到搓劫,是一層層的傳遞的,這里用一張圖解釋下上面的傳遞流程:


只考慮了Down和Up動作.png
只考慮了Down和Up動作.png

因為Up事件和Down事件是一樣的過程混巧,所以就不多說了枪向。
從上面的Log和流程圖我們可以得出幾點結(jié)論:
  • ACTION_DOWN事件從Activity#dispatchTouchEvent方法開始,然后一層層往下傳遞的牲剃,并且 ACTION_DOWN事件是否被消費的結(jié)果最終也是有dispatchTouchEvent傳回到Activity中遣疯。
  • ACTION_DOWN事件傳遞至ViewGroup#dispatchTouchEvent方法,ViewGroup#onInterceptTouchEvent返回false,表示不攔截ACTION_DOWN缠犀,如果返回的是true虐急,表示攔截不會再傳遞給子View滔迈,這個時候就會調(diào)用ViewGroup#onTouchEvent方法燎悍,判斷是否消費事件俄删,并回傳給Activity的#dispatchTouchEvent方法
  • ACTION_DOWN事件傳遞到View#dispatchTouchEvent方法鸽粉,在View#onTouchEvent進行執(zhí)行帚戳,返回true庐椒,表示事件已經(jīng)被消費笔宿,然后被回傳到View#dispatchTouchEvent,之后回傳到ACTION_DOWN事件的起點Activity#dispatchTouchEvent方法。如果這時候返回的是false的話重归,ViewGroup就是調(diào)用自己的onTouchEvent方法進行處理,如果依然返回false的話违柏,Activity就會調(diào)用自己的onTouchEvent方法處理(返回false的情況后面解釋)畜伐。
  • 如果某個View消費了ACTION_DOWN事件,那么這個事件序列中的后續(xù)事件也將交由其進行處理(有一些特殊情況除外,比如在序列中的之后事件進行攔截)

這里使用工作中的情況來模擬一下吧:老板(Activity)、項目經(jīng)理(ViewGroup)、軟件工程師(View)

  • 老板分配一個任務(wù)給項目經(jīng)理(Activity#dispatchTouchEvent→ViewGroup#dispatchTouchEvent),項目經(jīng)理選擇自己不做這個任務(wù)(ViewGroup#dispatchTouchEvent返回false),交由軟件工程師處理這個任務(wù)(View#dispatchTouchEvent)(我們忽略總監(jiān)與組長的情況),軟件工程師完成了這個任務(wù)(View#onTouchEvent返回true)
  • 把結(jié)果告訴項目經(jīng)理(返回結(jié)果true,View#dispatchTouchEvent→ViewGroup#dispatchTouchEvent),項目經(jīng)理把結(jié)果告訴老板(返回結(jié)果true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)
  • 項目經(jīng)理完成的不錯,老板決定把這個項目的二期、三期等都交給項目經(jīng)理,同樣項目經(jīng)理也覺得這個軟件工程師完成的不錯晒夹,所以也把二期、三期等都交給這個工程師來做

默認情況下View是消費觸摸事件的姊氓,現(xiàn)在我們再來看看當(dāng)View不消費觸摸事件(例如:clickable為false)的時候整個流程是怎么樣的:

View不消費觸摸事件.png
View不消費觸摸事件.png

流程圖:

ActivityDown.png
ActivityDown.png
ActivityUp.png
ActivityUp.png

原則:

  • 當(dāng)View沒有對Down動作進行事件消費的時候读跷,就交由父布局處理,如果父布局也沒有消費的時候就交由Activity處理
  • 如果View沒有對Down動作進行事件消費烫映,那么后續(xù)的動作也不在交由它處理识补,而是直接交由處理的了Down動作的容器進行處理族淮。

這里使用工作中的情況來模擬:依舊是老板(Activity)贴妻、項目經(jīng)理(ViewGroup)、軟件工程師(View) 從老板交任務(wù)給項目經(jīng)理蝙斜,項目經(jīng)理交任務(wù)給工程師名惩,這一段流程和之前的例子相同。不同之處是軟件工程師沒有完成這個任務(wù)(View#onTouchEvent返回false)孕荠,告訴項目經(jīng)理我沒有完成娩鹉,然后項目經(jīng)理自己進行了嘗試,同樣沒有完成(ViewGroup#onTouchEvent返回false)稚伍,項目經(jīng)理告訴了老板弯予,我沒有完成,然后老板自己試了下也沒有完成這個任務(wù)(Activity#onTouchEvent返回false),但之后的也有項目的二期个曙、三期锈嫩,不過老板知道你們完成不了,所以都是他自己進行嘗試垦搬,不過很慘都沒完成呼寸。(這段有點與正常情況不同,不過只是打個比方)

攔截事件

這里就直接貼圖看下Log吧:


攔截事件
攔截事件

流程圖:


Down
Down

Up
Up
結(jié)論
  • ViewGroup攔截事件之后猴贰,直接由自己的onTouchEvent()方法進行處理等舔,并最終將結(jié)果返回給Activity
  • ViewGroup攔截事件之后,后續(xù)的動作事件序列將不再調(diào)用onInterceptTouchEvent()判斷是否攔截糟趾,而是直接由自己進行處理慌植。

使用工作中的情況來模擬:老板(Activity)、項目經(jīng)理(ViewGroup)义郑、軟件工程師(View) 老板吧任務(wù)交給項目經(jīng)理蝶柿,項目經(jīng)理認為這個項目比較難,所以決定自己處理(ViewGroup#onInterceptTouchEvent,return true)非驮,項目經(jīng)理比較厲害他把任務(wù)完成了(ViewGroup#onTouchEvent,return true)交汤,然后他告訴老板他完成了(return true,ViewGroup#dispatchTouchEvent→Activity#dispatchTouchEvent)。之后老板依舊會把任務(wù)交給項目經(jīng)理劫笙,項目經(jīng)理知道這個任務(wù)難度芙扎,所以不假思索(也就是這個事件序列中的其余事件沒有經(jīng)過ViewGroup#onInterceptTouchEvent)的自己來做。

通過上面攔截的敘述就知道為什么在側(cè)邊欄打開的時候填大,不能點擊主界面了戒洼,因為觸摸事件被攔截了,所以只要進行好判斷在適當(dāng)?shù)臅r候不進行攔截就可以了允华。


圖片.png

至于點擊主界面不讓側(cè)邊欄收回圈浇,就讓主界面在動作為Activity_Up的時候消費事件(onTouchEvent()返回true)就可以了(因為DrawerLayout類在onTouchEvent()方法里關(guān)閉了側(cè)邊欄)


ViewGroup#onTouchEvent().png
DrawerLayout#onTouchEvent().png

最后在總結(jié)一下:

  • 一個事件序列是指從手指觸摸屏幕開始寥掐,到手指離開屏幕結(jié)束,這個過程中產(chǎn)生的一系列事件磷蜀。一個事件序列以ACTION_DOWN事件開始召耘,中間可能經(jīng)過若干個MOVE,以ACTION_UP事件結(jié)束褐隆。
  • 事件的傳遞過程是由外向內(nèi)的污它,即事件總是由父元素分發(fā)給子元素
  • 如果某個View消費了ACTION_DOWN事件,那么通常情況下庶弃,這個事件序列中的后續(xù)事件也將交由其進行處理轨蛤,但可以通過調(diào)用其父View的onInterceptTouchEvent方法,對后續(xù)事件進行攔截
  • 如果某個View它不消耗ACTION_DOWN事件虫埂,那么這個序列的后續(xù)事件也不會再交由它來處理
  • 如果事件沒有View對其進行處理祥山,那么最后將有Activity進行處理
  • 如果事件傳遞的結(jié)果為true,回傳的結(jié)果直接通過不斷調(diào)用父View#dispatchTouchEvent方法掉伏,傳遞給Activity缝呕;如果事件傳遞的結(jié)果為false,回傳的結(jié)果不斷調(diào)用父View#onTouchEvent方法斧散,獲取返回結(jié)果供常。
  • View默認的onTouchEvent在View可點擊的情況下,將會消耗事件鸡捐,返回true栈暇;不可點擊的情況下,則不消耗事件箍镜,返回false(longClickable的情況源祈,讀者可以自行測試,結(jié)果與clickable相同)
  • 如果某個ViewGroup的onInterceptTouchEvent返回為true色迂,那么這個事件序列中的后續(xù)事件香缺,不會在進行onInterceptTouchEvent的判斷,而是由它的dispatchTouchEvent方法直接傳遞給onTouchEvent方法進行處理
  • 如果某個View接收了ACTION_DOWN之后歇僧,這個序列的后續(xù)事件中图张,在某一刻被父View攔截了,則這個字View會收到一個ACTION_CANCEL事件诈悍,并且也不會再收到這個事件序列中的后續(xù)事件

項目源碼
參考文章
有問題歡迎聯(lián)系我(1021423736@qq.com)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祸轮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侥钳,更是在濱河造成了極大的恐慌适袜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慕趴,死亡現(xiàn)場離奇詭異痪蝇,居然都是意外死亡鄙陡,警方通過查閱死者的電腦和手機冕房,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門躏啰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耙册,你說我怎么就攤上這事给僵。” “怎么了详拙?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵帝际,是天一觀的道長。 經(jīng)常有香客問我饶辙,道長蹲诀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任弃揽,我火速辦了婚禮脯爪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矿微。我一直安慰自己痕慢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布涌矢。 她就那樣靜靜地躺著掖举,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娜庇。 梳的紋絲不亂的頭發(fā)上塔次,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音名秀,去河邊找鬼俺叭。 笑死,一個胖子當(dāng)著我的面吹牛泰偿,可吹牛的內(nèi)容都是我干的熄守。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耗跛,長吁一口氣:“原來是場噩夢啊……” “哼裕照!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起调塌,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤晋南,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后羔砾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體负间,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡偶妖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了政溃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趾访。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖董虱,靈堂內(nèi)的尸體忽然破棺而出扼鞋,到底是詐尸還是另有隱情,我是刑警寧澤愤诱,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布云头,位于F島的核電站,受9級特大地震影響淫半,放射性物質(zhì)發(fā)生泄漏溃槐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一科吭、第九天 我趴在偏房一處隱蔽的房頂上張望昏滴。 院中可真熱鬧,春花似錦砌溺、人聲如沸影涉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟹倾。三九已至,卻和暖如春猖闪,著一層夾襖步出監(jiān)牢的瞬間鲜棠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工培慌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留豁陆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓吵护,卻偏偏與公主長得像盒音,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子馅而,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

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