版權(quán)聲明:本文來自門心叼龍的博客苗踪,屬于原創(chuàng)內(nèi)容畜普,轉(zhuǎn)載請注明出處:https://blog.csdn.net/geduo_83/article/details/90145083
github源碼下載地址:https://github.com/geduo83/android-touch-event
Android事件分發(fā)機制的探索與發(fā)現(xiàn)之View篇
Android事件分發(fā)機制的探索與發(fā)現(xiàn)之ViewGroup篇
Android事件分發(fā)機制的探索與發(fā)現(xiàn)之Activity篇
Android事件分發(fā)機制的探索與發(fā)現(xiàn)之總結(jié)篇
Android事件分發(fā)機制在實戰(zhàn)開發(fā)中的應(yīng)用之一
Android事件分發(fā)機制在實戰(zhàn)開發(fā)中的應(yīng)用之二
關(guān)于Android事件傳遞機制的文章在前面也零零散散的寫過兩篇砸脊,當(dāng)時也是時間有限涡匀,也沒有過多的去完善匆匆就發(fā)出去了拴事。
因為Android事件分發(fā)機制是整個Android系統(tǒng)的難點也是重點,說它是難點是因為觸摸事件的流轉(zhuǎn)流程錯綜復(fù)雜弓摘,前一陣子在有個技術(shù)群里面很多人都說焚鹊,事件分發(fā)這塊自己也是學(xué)習(xí)了好多次,但是都是過一陣子韧献,對事件的流轉(zhuǎn)流程又忘了末患,記不住,學(xué)了又忘锤窑,忘了又學(xué)璧针,出現(xiàn)這種情況就是對事件流轉(zhuǎn)流程理解不深刻,對書上的流程圖死記硬背導(dǎo)致的渊啰,有些書上甚至出現(xiàn)流程錯誤的問題探橱,真是誤人子弟。
這種圖流程圖誰能記得住绘证,而且還有一張View的我就不貼圖了隧膏,正是有些作者的不負(fù)責(zé)任,讓很多人走了不少的彎路嚷那,這就是研究問題的方法有問題胞枕,就好比有的老師可能半個小時就把這個問題將清楚,而有的老師可能花好幾個小時把這個問題還講不明白魏宽,就是因為他們沒有透過現(xiàn)象看到問題的本質(zhì)所在腐泻,本來問題就比較復(fù)雜决乎,再復(fù)雜的問題也是有規(guī)律可循的。
說它是重點贫悄,我們知道在功能機時代瑞驱,我們與手機交互主要是依靠機械鍵盤來進(jìn)行輸入,而在智能機時代窄坦,觸摸屏技術(shù)的革命,人與手機的交互都變得那么的簡單凳寺,輕輕的點擊手指就能完成各種各種的復(fù)雜操作鸭津,從簡單的點擊一個按鈕,界面切換的左滑右滑操作肠缨,再到瀏覽新聞看更多內(nèi)容的上滑下滑操作逆趋,這樣的操作可以說在現(xiàn)在的智能手機上無處不在,這一切的操作的背后晒奕,就是整個Android操作系統(tǒng)的事件分發(fā)機制闻书。
所以,也不是一兩篇文章就能講清楚的脑慧,所以今天我申請了一個關(guān)于事件分發(fā)的專題魄眉,準(zhǔn)備通過一系列的文章來講解View的事件分發(fā)機制。網(wǎng)上關(guān)于事件分發(fā)的文章可以說都是鋪天蓋地闷袒,我看大部分都是講源碼分析坑律,有的有理論沒實戰(zhàn),有的只講結(jié)果不講分析囊骤,有的是講了View晃择、ViewGroup,而沒有不講Activity也物。
說到事件分發(fā)宫屠,我們就是要搞清楚事件分發(fā)的流程。事件分發(fā)滑蚯,可以拆開為兩部分浪蹂,一個是事件,一個是分發(fā)膘魄。何為事件乌逐?當(dāng)你用的手指在屏幕上點擊了一下,滑動了一下创葡,有幾個事件浙踢?都是什么事件?
觸摸事件類型
觸摸事件對應(yīng)的是MotionEvent,事件主要有如下三種
- ACTION_DOWN:用戶手指按下的操作灿渴,標(biāo)志一次觸摸事件的開始
- ACITON_MOVE:用戶在手指按下屏幕之后洛波,在松開之前胰舆,如果移動的距離超過一定的閾值,就會被認(rèn)定為ACTION_MOVE操作
- ACTION_UP:用戶手指抬起的操作蹬挤,標(biāo)志一次觸摸事件的結(jié)束
觸摸事件傳遞
講完了事件缚窿,接下來我們說分發(fā),事件的分發(fā)焰扳,實質(zhì)就是講事件的傳遞倦零,事件的傳遞分為三個階段:
- 分發(fā):dispatchTouchEvent(MotionEvent event),在Android系統(tǒng)中吨悍,所有的觸摸事件都是通過這個方法來分發(fā)的
- 攔截:onInterceptTouchEvent扫茅,這個方法只有ViewGroup中存在
- 消費:onTouchEvent
觸摸事件的相關(guān)類
在android系統(tǒng)中,擁有事件傳遞處理能力的類有三個
- Activity:擁有dispatchTouchEvent和onTouchEvent方法
- View:和Activity一樣同時擁有dispatchTouchEvent和onTouchEvent方法
- ViewGourp:擁有dispatchTouchEvent和onTouchEvent方法育瓜,在加上一個攔截方法onInterceptTouchEvent
View和ViewGourp之間的關(guān)系
View是ViewGourp的父類葫隙,一個ViewGroup中可以擁有多個View和ViewGourp,下面谷歌android API 文檔的一個截圖躏仇,可以清楚的看到他們之間的一個繼承關(guān)系前面我在講事件傳遞的時候恋脚,主要講了三個方法,事件分發(fā)dispathTouchEvent方法焰手,事件攔截的onInterecptTouchEvent方法和事件消費的onTouch的onTouchEvent方法糟描。
以上就是三個關(guān)于觸摸事件的處理方法,我們通過谷歌官方api文檔的截圖我們不難發(fā)現(xiàn)册倒,他們都是返回的是一個boolean類型的常量蚓挤,無外乎要么返回true,要么返回false驻子,還有一種可能就是調(diào)用父類的同名方法灿意,通過接口文檔我們得知dispathTouchEvent返回true表示該事件被View處理了,反之返回false就是不處理崇呵,不處理那么事件最后交給誰了缤剧?onTouchEvent方法返回true這是表示被消費掉了,反之亦然域慷,那么調(diào)用它父類的同名方法結(jié)果又會怎么樣荒辕?關(guān)于攔截方法如果返回true表示被攔截掉了,那么這個事件是交給了自己的onTouchEvent方法了嗎犹褒?如果onTouchEvent返回了false不消費掉這給事件又會交給誰去處理抵窒?等等一系列的疑問。
但是不管怎么樣我們通過官方文檔我們可以得到一個結(jié)論就是:不同的返回值會導(dǎo)致事件的傳遞流程發(fā)生很大的變化叠骑,我們想了李皇,我們通過不斷的修改這些方法的返回值,并查看日志記錄宙枷,我們就可以清楚的看到每一種情況觸摸事件的處理流程掉房。
為此我準(zhǔn)備了兩個自定義控件MyLayout和MyButton茧跋,并復(fù)寫他們的dispathchTouchEvent方法和onTouchEvent方法,以及onInterceptTouEvent方法卓囚,不斷的修改他們的返回值瘾杭,來跟蹤每個事件的處理流程。
準(zhǔn)備工作:
- 自定義ViewGroup MyLayout.java
package com.zjx.vcar.test.view;
public class MyLayout extends LinearLayout {
public static final String TAG = "MYTAG";
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyLayout dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyLayout onInterceptTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MyLayout onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
- 自定義View MyButton.java
@SuppressLint("AppCompatCustomView")
public class MyButton extends Button {
public static final String TAG = "MYTAG";
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MyButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
- 創(chuàng)建MainActivty.java的布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.touchevent.view.MyLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#FF00FF"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.touchevent.view.MyButton
android:layout_width="200dp"
android:layout_height="100dp"
android:text="點擊測試"
android:background="#00FF00"
/>
</com.android.touchevent.view.MyLayout>
- 創(chuàng)建MainActivity.java
public class MainActivity extends AppCompatActivity {
public static String TAG = "MYTAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MainActivity dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MainActivity onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
}
好了哪亿,截止目前測試的準(zhǔn)備工作就做好了粥烁,我們看下頁面效果,MyLayout設(shè)置了紫色背景锣夹,MyButon設(shè)置了綠色背景
本文我們只分析MyButton的處理流程页徐,MyLayout和Activity的流程我們放在后面的章節(jié)進(jìn)行介紹,這樣是為了避免文章過長银萍,反而不利于大家的學(xué)習(xí)。
MyButton的onTouchEvent方法
- 情況1:我們onTouchEvent調(diào)用父類的同名方法
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MyButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return super.onTouchEvent(event);
}
- 點擊測試按鈕測試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_MOVE
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MyButton onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyButton onTouchEvent start:ACTION_UP
MyButton 的onTouchEvent方法依次接受到了三個事件:ACTION_DOWN,ACTION_MOVE,ACTION_MOVE
- 情況2:我們onTouchEvent返回true
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MyButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return true;
}
- 點擊測試按鈕測試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton onTouchEvent start:ACTION_DOWN
-----------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyButton onTouchEvent start:ACTION_UP
通過測試打印的log我們不難發(fā)現(xiàn)恤左,調(diào)用父類的同名方法和返回true其結(jié)果是一樣的
- 情況3:返回false
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.v(TAG,"MyButton onTouchEvent start:"+MotionEventUtil.getMotionEventName(event));
return false;
}
點擊測試結(jié)果:
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton onTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onTouchEvent start:ACTION_DOWN
V/MYTAG: MainActivity onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MainActivity onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MainActivity onTouchEvent start:ACTION_UP
我們驚奇的發(fā)現(xiàn)如果返回false贴唇,MyButton的onTouchEvent方法將事件傳遞給MyLayout的onTourchEvent方法了,也就是事件回傳了飞袋,如果MyLayout不消費的話戳气,將繼續(xù)回傳,直到MainActivity的onTouchEvent方法巧鸭,后續(xù)的ACTION_MOVE瓶您,ACTION_UP,就不在分發(fā)了纲仍,MainActivity的onTouchEvent就直接消費掉了呀袱。
MyButton的dispatchTouchEvent方法
- 情況1:dispatchTouchEvent調(diào)用父類的同名方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return super.dispatchTouchEvent(ev);
}
點擊測試結(jié)果我們不用測試就能猜得到了,因為我們測試它的onTouchEvent方法的時候郑叠,dispatchTouchEvent方法默認(rèn)就調(diào)用的是父類的同名方法super.dispatchTouchEvent(ev)夜赵,我們通過觀察打印日志不難發(fā)現(xiàn),MyButton的dispatchTouchEvent方法執(zhí)行完畢乡革,就把點擊事件傳遞給自己的onTouchEvent方法了寇僧,也就是說MyButton沒有子view了,就把事件分發(fā)給自己onTouchEvent方法了
- 情況2:返回true
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return true;
}
點擊測試按鈕
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_DOWN
------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_UP
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_UP
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_UP
返回true沸版,點擊事件被MyButton的dispatchTouchEvent方法自行消費掉了
- 情況3:返回false
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.v(TAG,"MyButton dispatchTouchEvent start:"+MotionEventUtil.getMotionEventName(ev));
return false;
}
點擊測試
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onInterceptTouchEvent start:ACTION_DOWN
V/MYTAG: MyButton dispatchTouchEvent start:ACTION_DOWN
V/MYTAG: MyLayout onTouchEvent start:ACTION_DOWN
V/MYTAG: MainActivity onTouchEvent start:ACTION_DOWN
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_MOVE
V/MYTAG: MainActivity onTouchEvent start:ACTION_MOVE
---------------------------------------------------------
V/MYTAG: MainActivity dispatchTouchEvent start:ACTION_UP
V/MYTAG: MainActivity onTouchEvent start:ACTION_UP
當(dāng)MyButton的dispatchTouchEvent方法返回false的時候嘁傀,點擊事件是一路回傳,直到到達(dá)MainActivity onTouchEvent的方法才算結(jié)束
好了视粮,直到現(xiàn)在我們對MyButton的dispatchTouchEvent方法和onTouchEvent方法就徹底分析完了细办,我們不難得出結(jié)論:觸摸事件是按照View樹的嵌套層次由內(nèi)到外依次進(jìn)行傳遞,直到到達(dá)最內(nèi)層的子View的onTouchEvent方法馒铃,如果onTouchEvent方法返回true或者調(diào)用了父類的同名方法蟹腾,表示該事件被消費掉了痕惋,如果返回了false則該觸摸事件會回傳回去,直到到達(dá)最外層的MainActivity的onTouchEvent方法娃殖,對于dispatchTouchEvent方法如果調(diào)用的是父類的同名方法則觸摸事件會分發(fā)給自己的onTouchEvent方法值戳,如果返回了true則表示該方法被自行消費掉了,如果返回了false表示該觸摸事件會一層一層的回傳給自己父View的onTouchEvent方法炉爆,直到到達(dá)MainActivity的onTouchEvent方法才算結(jié)束堕虹,此后的其他事件如ACTION_MOVE和ACTION_DOWN事件直接就被MainActivity的onTouchEvent方法消費掉了,而不會傳遞給MyLayout了