Android觸摸事件傳遞機(jī)制
前言:在Android開發(fā)中,經(jīng)常會(huì)遇到觸摸事件沖突,比如ViewPager的輪播圖跟Fragment的劃動(dòng)事件沖突,或者輪播圖跟下拉事件沖突,自定義view的事件處理等,本文章將會(huì)詳細(xì)介紹Activity、View、ViewGroup三者的觸摸事件傳遞機(jī)制,傳遞包括三個(gè)階段:分發(fā)、攔截、消費(fèi)。
本文章將會(huì)詳細(xì)介紹Activity、View、ViewGroup三者的觸摸事件傳遞機(jī)制,傳遞包括三個(gè)階段:分發(fā)、攔截、消費(fèi)。
一.觸摸事件的類型
觸摸事件對(duì)應(yīng)的是 MotionEvent 類,事件類型主要有三種:
- ACTION_DOWN:用戶按下操作,表示一次觸摸事件的開始。
- ACTION_MOVE:在按下的情況下,進(jìn)行移動(dòng)。輕微的移動(dòng)都會(huì)傳遞到該事件。
- ACTION_UP:用戶手指離開屏幕,表示一次觸摸事件的
注 :如果用戶僅僅的是點(diǎn)擊而已,則只會(huì)執(zhí)行到 ACTION_DOWN 和 ACTION_UP 兩個(gè)事件,不會(huì)執(zhí)行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必須的。
二.觸摸事件的傳遞階段
1.分發(fā)(Dispatch)
在Android系統(tǒng)中所有的觸摸事件都是由 dispatchTouchEvent 方法進(jìn)行分發(fā)的。該方法中判斷事件是被消費(fèi)( return true ),還是繼續(xù)分發(fā)給子視圖處理( return super.dispatchTouchEvent ),如果當(dāng)前視圖是ViewGroup或者其子類,則會(huì)調(diào)用 onInterceptTouchEvent 判斷是否截?cái)r。
@Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
2.截?cái)r(Intercept)
事件的截?cái)r InterceptTouchEvent 只存在于ViewGroup及其子類,activity和View是不存在該方法。該方法判斷事件是被截?cái)r ( return true )并交給自身的 OnToucEvent 方法進(jìn)行消費(fèi),還是繼續(xù)傳遞給子視圖( return super.InterceptTouchEvent 或者 return false )。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); }
3.消費(fèi)(Consume)
事件的消費(fèi)通過 OnTouchEvent 方法判斷,是被消費(fèi)( return true ),還是不處理( return false )并將事件傳遞給父視圖的 OnTouchEvent 方法進(jìn)行處理。
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
所有擁有事件傳遞能力的類:
Activity: 擁有dispatchTouchEvent 、OnTouchEvent
ViewGroup: 擁有dispatchTouchEvent 、OnInterceptTouchEvent 、OnTouchEvent
View:擁有dispatchTouchEvent 、OnTouchEvent
三、View的事件傳遞機(jī)制
3.1 dome
雖然說ViewGroup是View的子類,但是這是說的View指的是除ViewGroup之外的View控件子類,首先定義一個(gè)MyTextView繼承TextView,打印每次事件的觸發(fā)以變了解事件傳遞的流程。
MyTextView 類
public class MyTextView extends TextView { private String tag = "MyTextView"; public MyTextView(Context context) { super(context); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
定義一個(gè)MainActivity來展現(xiàn)這個(gè)MyTextView,同時(shí)設(shè)置點(diǎn)擊(onClick)和觸摸(onTouch)監(jiān)聽。 MainActivity 類
public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{ private MyTextView mMyTextView; private String tag = "MainActiviy"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMyTextView = findViewById(R.id.text_view); // 點(diǎn)擊監(jiān)聽 mMyTextView.setOnClickListener(this); // 觸碰監(jiān)聽 mMyTextView.setOnTouchListener(this); } // MyTextView 點(diǎn)擊事件 @Override public void onClick(View view) { switch (view.getId()){ case R.id.text_view: Log.i(tag, "MyTextView onClick"); break; } } // MyTextView 觸碰事件 @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "MyTextView onTouch ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "MyTextView onTouch ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "MyTextView onTouch ACTION_DOWN"); break; } return false; } // Activity 的事件分發(fā) @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } // Activity 的事件消費(fèi) @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
3.2 打印日志
運(yùn)行后,點(diǎn)擊Text View反饋的打印日志
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
03-28 08:05:15.044 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
dispatchTouchEvent 、 OnTouchEvent 這兩個(gè)方法的返回值存在三種情況:
- 直接返回true。
- 直接返回false。
- 返回父類同名方法,super.dispatchTouchEvent 或者 super.OnTouchEvent。
由于擁有不同的返回值,所以事件傳遞流程也有不同,經(jīng)過不斷修改返回值測試,最終得到了點(diǎn)擊事件的流程圖,ACTION_DOWN 和 ACTION_UP 事件的傳遞流程是相同的。
3.3 事件傳遞流程圖
從上面的流程圖可以得出結(jié)論:
- 觸摸事件是從 dispatchTouchEvent 開始的,默認(rèn)返回父類同名方法 super ,事件將會(huì)依照嵌套層次從外向內(nèi)傳遞( MainActivity 到 MyTextView ),到達(dá)最內(nèi)層的 View 時(shí),將由 View 的 OnTouchEvent 方法處理,該方法返回 true 時(shí)進(jìn)行消費(fèi)不再傳遞,返回 false 時(shí)再由內(nèi)向外傳遞,由外層的 OnTouchEvent 處理。
- 如果外層向內(nèi)層傳遞過程中,人為干擾返回 true 消費(fèi),則不會(huì)繼續(xù)繼續(xù)像內(nèi)部傳遞。
- View 的事件控制順序先執(zhí)行 onTouch 再執(zhí)行 onClick ,如果 onTouch 返回 true 消費(fèi),則不會(huì)繼續(xù)傳遞,也不會(huì)執(zhí)行 onClick 方法。
四、ViewGroup的事件傳遞機(jī)制
4.1 dome
ViewGroup是 View 的控件容器存在,擁有 dispatchTouchEvent 、 onInterceptTouchEvent 和 onTouchEvent 三個(gè)方法,比 View 多了一個(gè) onInterceptTouchEvent 方法。為了更好的觀察,我們需要自定義 MyRelativeLayout 繼承 RelativeLayout 。
MyRelativeLayout類
public class MyRelativeLayout extends RelativeLayout { private final static String tag = "MyRelativeLayout"; public MyRelativeLayout(Context context) { super(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "dispatchTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "dispatchTouchEvent ACTION_DOWN"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onInterceptTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onInterceptTouchEvent ACTION_DOWN"); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_UP: Log.i(tag, "onTouchEvent ACTION_UP"); break; case MotionEvent.ACTION_MOVE: Log.i(tag, "onTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: Log.i(tag, "onTouchEvent ACTION_DOWN"); break; } return super.onTouchEvent(event); } }
main_activity.xml 文件
<?xml version="1.0" encoding="utf-8"?> <com.mvp.chenzhesheng.androidadvance.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.mvp.chenzhesheng.androidadvance.MyTextView android:id="@+id/text_view" android:clickable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" </com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>
4.2 打印日志
04-02 08:47:57.980 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
04-02 08:47:58.260 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick
可以看到 MainActivity 和 MyTextView 的事件傳遞處理中添加了一層 MyRelativeLayout 。通過不同返回值測試,得到一套流程圖。
4.3 流程圖
從上面的流程圖可以得出結(jié)論:
- 觸摸事件傳遞是從 Activity 傳遞到 ViewGroup ,再傳遞到 View 。如果中間沒有 ViewGroup 則直接從 Activity 傳遞到 View 。
- ViewGroup 通過 onInterceptTouchEvent 方法對(duì)事件進(jìn)行截?cái)r,如果返回 false 或者 super.onInterceptTouchEvent ,則事件會(huì)繼續(xù)傳遞給子 View 。
- 子 View 中對(duì)事件進(jìn)行消費(fèi)后, ViewGroup 將不會(huì)接收到任何事件。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
一個(gè)強(qiáng)大的側(cè)滑菜單控件ASwipeLayout
這篇文章主要為大家詳細(xì)介紹了強(qiáng)大的側(cè)滑菜單控件ASwipeLayout使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android原生定位服務(wù)LocationManager
這篇文章主要為大家介紹了Android原生定位服務(wù)LocationManager實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Android實(shí)現(xiàn)短信加密功能(發(fā)送加密短信、解密本地短信)
這篇文章主要介紹了android實(shí)現(xiàn)短信加密功能的相關(guān)資料,功能包括發(fā)送加密短信、解密本地短信,感興趣的小伙伴們可以參考一下2016-01-01Android實(shí)現(xiàn)可以展開的TextView
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)可以展開的TextView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android繪制旋轉(zhuǎn)動(dòng)畫方法詳解
這篇文章主要介紹了Android如何采用RotateAnimation繪制一個(gè)旋轉(zhuǎn)動(dòng)畫,文中的實(shí)現(xiàn)方法講解詳細(xì),感興趣的小伙伴可以跟隨小編一起試一試2022-01-01Android保存多張圖片到本地的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android保存多張圖片到本地的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06MPAndroidChart繪制自定義運(yùn)動(dòng)數(shù)據(jù)圖表示例詳解
這篇文章主要為大家介紹了MPAndroidChart繪制自定義運(yùn)動(dòng)數(shù)據(jù)圖表示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09android獲取手機(jī)IMSI碼判斷手機(jī)運(yùn)營商代碼實(shí)例
這篇文章主要介紹了android獲取手機(jī)IMSI碼判斷手機(jī)運(yùn)營商代碼實(shí)例,大家參考使用2013-11-11Android 菜單欄DIY實(shí)現(xiàn)效果詳解
這篇文章主要為大家介紹了Android 菜單欄DIY實(shí)現(xiàn)效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09