關(guān)于Android觸摸事件分發(fā)的原理詳析
一:前言
最近在學(xué)Android的觸摸事件分發(fā),我覺得網(wǎng)上說的太雜太亂,而且有很多博客都有明顯的錯(cuò)誤。什么自頂向下分發(fā),自下向頂分發(fā),什么攔截又一直消費(fèi)什么什么之類,非常難懂。為了自己將來回顧可以更好的理解這塊知識(shí),也為了后來之人可以更好的學(xué)習(xí),我寫下這篇博客。
二:說在前面的知識(shí)
- 點(diǎn)擊,滑動(dòng),松手都是由MotionEvent這個(gè)類來表示。
- 屏幕上的一個(gè)事件序列是指以一個(gè)MotionEvent.action_down按下開始,以若干個(gè)MotionEvent.action_move移動(dòng)事件在中間,再以一個(gè)MotionEvent.action_up作為結(jié)束的事件流。
- view group是view的子類。view group和view都有dispatchTouchEvent方法;view group有onTnterceptTouchEvent和onTouchEvent方法,view 只有onTouchEvent方法。
三:整體流程
1:activity
我們點(diǎn)擊屏幕的所有事件,都會(huì)被第一個(gè)接收。
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//是一個(gè)空方法,如果想知道按下了屏幕,可以重寫這個(gè)方法打印日志 } if (getWindow().superDispatchTouchEvent(ev)) {//把這個(gè)事件傳給window屬性 return true; } return onTouchEvent(ev); }
2:window就是PhoneWindow
每一個(gè)activity都會(huì)對(duì)應(yīng)一個(gè)PhoneWindow(在onCreate方法之前、activity內(nèi)部的attach方法中創(chuàng)建)。PhoneWindow含有一個(gè)decor view屬性(setContentView中創(chuàng)建),phone window把事件傳給decor view。 decor view繼承于view group。點(diǎn)擊事件現(xiàn)在傳到decor view這里,就開始view group的事件分發(fā)邏輯了。
3:view group
view group收到點(diǎn)擊事件, 進(jìn)入dispatchTouchEvent, 如果滿足以下二個(gè)條件中的任何一個(gè)條件:
- 事件為down事件
- 有一個(gè)子view或子view group在處理著事件流了
mFirstTouchTarget !=null
就進(jìn)入判斷,如果沒有被禁用攔截(子view調(diào)用parent.requestDisallowed....)就執(zhí)行, onInterceptTouchEvent代碼。
如果決定攔截,后面還會(huì)把mFirstTouchTarget置為null,這樣,之后就不會(huì)在調(diào)用onInterceptTouchEvent了。而且之后的事件流都會(huì)由這個(gè)view group的dispatchTouchEvent處理
如果不決定攔截,就遍歷子view、子view group,挨個(gè)調(diào)用它們的dispatchTouchEvent。如果沒有人接收,那就調(diào)用自己的super.dispatchTouchEvent. view group的super.dispatchTouchEvent就是自己view那部分 的 dispatchTouchEvent。
4:view
在view這一層,對(duì)于down事件,返回true就表示消費(fèi)這個(gè)down事件之后的序列。具體看圖。
view調(diào)用setOnTouchLIstener可以設(shè)置OnTouchListener,重寫onTouch方法。從源碼中可以看出,若onTouch返回true,將不再回調(diào)onTouchEvent方法。不回調(diào)onTouchEvent的話,那onClickListener也不能回調(diào)了。
四:一些關(guān)鍵點(diǎn)
即使有view消費(fèi)著一組事件,事件流由底向上傳遞時(shí),依然會(huì)調(diào)用每一個(gè)view group的intercept攔截方法判斷是否攔截。當(dāng)一個(gè)view group遍歷它所有的子view沒有一個(gè)接收時(shí),就會(huì)進(jìn)入view模式,調(diào)用自己繼承于view的那一個(gè)dispatchTouchEvent方法。如果自己不接收,那會(huì)交給調(diào)用自己的dispatchTouchEvent的那個(gè)父view.
事件流沒有什么自上而下,就是自下而上的。
ViewGroup的實(shí)現(xiàn)負(fù)責(zé)將觸摸事件沿著控件樹向子控件進(jìn)行派發(fā),而View的實(shí)現(xiàn)則主要用于事件接收與處理工作。當(dāng)view group沒有子view接收時(shí),view group作為一個(gè)“view”去處理。
五:從源碼看觸摸事件分發(fā)
由于專欄關(guān)注自定義控件,所以關(guān)于系統(tǒng)如何從硬件獲取觸摸事件以及傳遞到Activity的dispatchTouchEvent就不詳細(xì)分解,下面將從Activity的dispatchTouchEvent方法來一步步看事件是如何被分發(fā)傳遞的:
Activity中的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
其中onUserInteraction();是一個(gè)空實(shí)現(xiàn),是系統(tǒng)留給我們的一個(gè)修改事件分發(fā)的一個(gè)方法,這里可以忽略。
所以實(shí)際上Activity的dispatchTouchEvent方法是調(diào)用的PhoneWindow的superDispatchTouchEvent方法,如果superDispatchTouchEvent返回false,沒有消費(fèi)掉事件,那么才會(huì)再交給activity的onTouchEvent方法去處理,從這個(gè)角度來講,如果所有地方都沒有消費(fèi)掉事件,最后接收事件的會(huì)是Activity的onTouchEvent方法。
那么下面我們來看看PhoneWindow中的superDispatchTouchEvent方法:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
發(fā)現(xiàn)實(shí)際上調(diào)用的是DecorView對(duì)象mDecor的superDispatchTouchEvent方法,來看看DecorView的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
調(diào)用的super.dispatchTouchEvent,而再來看看這個(gè)DecorView的繼承關(guān)系:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
所以調(diào)用的是FrameLayout中的dispatchTouchEvent方法,而FrameLayout并沒有重寫dispatchTouchEvent方法,所以實(shí)際調(diào)用的是FrameLayout的父類 ---> ViewGroup中的dispatchTouchEvent方法,下面這個(gè)圖描述了從系統(tǒng)得到MotionEvent實(shí)際到傳遞給DecorView的super.dispatchTouchEvent的過程:
總結(jié)
到此這篇關(guān)于Android觸摸事件分發(fā)原理的文章就介紹到這了,更多相關(guān)Android觸摸事件分發(fā)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot實(shí)現(xiàn)郵件服務(wù)(附:常見郵箱的配置)
這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)郵件服務(wù)的相關(guān)資料,文中還附上了常見郵箱的配置,通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12詳解springmvc之json數(shù)據(jù)交互controller方法返回值為簡(jiǎn)單類型
這篇文章主要介紹了springmvc之json數(shù)據(jù)交互controller方法返回值為簡(jiǎn)單類型,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05深入分析JAVA 多線程--interrupt()和線程終止方式
這篇文章主要介紹了JAVA 多線程--interrupt()和線程終止方式的的相關(guān)資料,文中代碼非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06Spring的請(qǐng)求映射handlerMapping以及原理詳解
這篇文章主要介紹了Spring的請(qǐng)求映射handlerMapping以及原理詳解,我們每次發(fā)請(qǐng)求,它到底是怎么找到我們哪個(gè)方法來去處理這個(gè)請(qǐng)求,因?yàn)槲覀冎浪械恼?qǐng)求過來都會(huì)來到DispatcherServlet,springboot底層還是使用的是springMVC,需要的朋友可以參考下2023-08-08MybatisPlus使用注解的多對(duì)多級(jí)聯(lián)查詢方式
這篇文章主要介紹了MybatisPlus使用注解的多對(duì)多級(jí)聯(lián)查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它
這篇文章主要介紹了Java統(tǒng)計(jì)輸入字符的英文字母、空格、數(shù)字和其它,需要的朋友可以參考下2017-02-02