Android進(jìn)階之View事件分發(fā)機(jī)制和源碼詳解
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」,作者Android開發(fā)編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號(hào)。
前言
在Android 開發(fā)中事件分發(fā)是比較重要的,也是比較難理解的;
開發(fā)中會(huì)經(jīng)常遇到滑動(dòng)沖突(比如ScrollView或是SliddingMenu與ListView的嵌套)的問題,
需要我們深入的了解android事件響應(yīng)機(jī)制才能解決,
事件響應(yīng)機(jī)制已經(jīng)是android開發(fā)者必不可少的知識(shí)。
那么今天我們就來詳細(xì)講解下事件分發(fā),各位老鐵們一起學(xué)習(xí)
一、view事件相關(guān)知識(shí)了解
1、View的位置參數(shù)
View的位置主要由它的四個(gè)頂點(diǎn)來決定,即它的四個(gè)屬性:top、left、right、bottom,分別表示View左上角的坐標(biāo)點(diǎn)( top,left) 以及右下角的坐標(biāo)點(diǎn)( right,bottom)。
同時(shí),我們可以得到View的大?。?/p>
- width = right - left
- height = bottom - top
而這四個(gè)參數(shù)可以由以下方式獲取:
- Left = getLeft();
- Right = getRight();
- Top = getTop();
- Bottom = getBottom();
Android3.0后,View增加了x、y、translationX和translationY這幾個(gè)參數(shù)。其中x和y是View左上角的坐標(biāo),而translationX和translationY是View左上角相對(duì)于容器的偏移量。他們之間的換算關(guān)系如下:
- x = left + translationX;
- y = top + translationY;
2、MotionEvent
我們對(duì)屏幕的點(diǎn)擊,滑動(dòng),抬起等一系的動(dòng)作都是由一個(gè)一個(gè)MotionEvent對(duì)象組成的。根據(jù)不同動(dòng)作,主要有以下三種事件類型:
- ACTION_DOWN:手指剛接觸屏幕,按下去的那一瞬間產(chǎn)生該事件
- ACTION_MOVE:手指在屏幕上移動(dòng)時(shí)候產(chǎn)生該事件
- ACTION_UP:手指從屏幕上松開的瞬間產(chǎn)生該事件
從ACTION_DOWN開始到ACTION_UP結(jié)束我們稱為一個(gè)事件序列
正常情況下,無論你手指在屏幕上有多么騷的操作,最終呈現(xiàn)在MotionEvent上來講無外乎下面兩種;
- 點(diǎn)擊后抬起,也就是單擊操作:ACTION_DOWN -> ACTION_UP
- 點(diǎn)擊后再風(fēng)騷的滑動(dòng)一段距離,再抬起:ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_MOVE -> ACTION_UP;
- public class MotionEventActivity extends BaseActivity {
- private Button mButton;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_motion_event);
- mButton = (Button) findViewById(R.id.button);
- mButton.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- e("MotionEvent: ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- e("MotionEvent: ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- e("MotionEvent: ACTION_UP");
- break;
- }
- return false;
- }
- });
- }
- public void click(View v) {
- e("點(diǎn)擊了按鈕");
- }
- }
3、Scroller
彈性滑動(dòng)對(duì)象,用于實(shí)現(xiàn)View的彈性滑動(dòng)。其本身無法讓View彈性滑動(dòng),需要和View的computeScroll方法配合使用才能完成這個(gè)功能。使用方法:
- Scroller scroller = new Scroller(mContext);
- //緩慢移動(dòng)到指定位置
- private void smoothScrollTo(int destX,int destY){
- int scrollX = getScrollX();
- int delta = destX - scrollX;
- //1000ms內(nèi)滑向destX,效果就是慢慢滑動(dòng)
- mScroller.startScroll(scrollX,0,delta,0,1000);
- invalidata();
- }
- @Override
- public void computeScroll(){
- if(mScroller.computeScrollOffset()){
- scrollTo(mScroller.getCurrX,mScroller.getCurrY());
- postInvalidate();
- }
- }
4、事件分發(fā)涉及方法了解
- dispatchTouchEvent(MotionEvent ev):用來進(jìn)行事件分發(fā)。如果事件能傳遞給當(dāng)前的View,那么此方法一定會(huì)被調(diào)用。
- onInterceptTouchEvent(MotionEvent ev):用來判斷是否攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件,那么在同一個(gè)事件序列中,此方法不會(huì)再被調(diào)用。
- onTouchEvent(MotionEvent ev):用來處理觸摸事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無法再次接受事件。
- onTouch(View view, MotionEvent motionEvent):用于處理觸摸事件,通過setOnTouchListener設(shè)置,很常見的方法。
- onClick(View view):用于處理點(diǎn)擊事件,通過setOnClickListener設(shè)置,很常見的方法。
- requestDisallowInterceptTouchEvent(boolean b):請(qǐng)求不攔截觸摸事件。一般用于處理滑動(dòng)沖突中,子控件請(qǐng)求父控件不攔截;
- ACTION_DOWN以外的其他事件,ACTION_DOWN事件不受影響。
二、事件分發(fā)
當(dāng)一個(gè)MotionEvent產(chǎn)生了以后,就是你的手指在屏幕上做一系列動(dòng)作的時(shí)候,系統(tǒng)需要把這一系列的MotionEvent分發(fā)給一個(gè)具體的View。我們重點(diǎn)需要了解這個(gè)分發(fā)的過程,那么系統(tǒng)是如何去判斷這個(gè)事件要給哪個(gè)View,也就是說是如何進(jìn)行分發(fā)的呢?
事件分發(fā)需要View的三個(gè)重要方法來共同完成:
1、public boolean dispatchTouchEvent(MotionEvent event)
通過方法名我們不難猜測(cè),它就是事件分發(fā)的重要方法。那么很明顯,如果一個(gè)MotionEvent傳遞給了View,那么dispatchTouchEvent方法一定會(huì)被調(diào)用!
返回值:表示是否消費(fèi)了當(dāng)前事件??赡苁荲iew本身的onTouchEvent方法消費(fèi),也可能是子View的dispatchTouchEvent方法中消費(fèi)。返回true表示事件被消費(fèi),本次的事件終止。返回false表示View以及子View均沒有消費(fèi)事件,將調(diào)用父View的onTouchEvent方法;
2、public boolean onInterceptTouchEvent(MotionEvent ev)
事件攔截,當(dāng)一個(gè)ViewGroup在接到MotionEvent事件序列時(shí)候,首先會(huì)調(diào)用此方法判斷是否需要攔截。特別注意,這是ViewGroup特有的方法,View并沒有攔截方法;
返回值:是否攔截事件傳遞,返回true表示攔截了事件,那么事件將不再向下分發(fā)而是調(diào)用View本身的onTouchEvent方法。返回false表示不做攔截,事件將向下分發(fā)到子View的dispatchTouchEvent方法。
3、public boolean onTouchEvent(MotionEvent ev)
真正對(duì)MotionEvent進(jìn)行處理或者說消費(fèi)的方法。在dispatchTouchEvent進(jìn)行調(diào)用;
返回值:返回true表示事件被消費(fèi),本次的事件終止。返回false表示事件沒有被消費(fèi),將調(diào)用父View的onTouchEvent方法
上面的三個(gè)方法可以用以下的偽代碼來表示其之間的關(guān)系。
- public boolean dispatchTouchEvent(MotionEvent ev) {
- boolean consume = false;//事件是否被消費(fèi)
- if (onInterceptTouchEvent(ev)){//調(diào)用onInterceptTouchEvent判斷是否攔截事件
- consume = onTouchEvent(ev);//如果攔截則調(diào)用自身的onTouchEvent方法
- }else{
- consume = child.dispatchTouchEvent(ev);//不攔截調(diào)用子View的dispatchTouchEvent方法
- }
- return consume;//返回值表示事件是否被消費(fèi),true事件終止,false調(diào)用父View的onTouchEvent方法
- }
接下來我們來看一下View 和ViewGroup 在事件分發(fā)的時(shí)候有什么不一樣的地方
ViewGroup是View的子類,也就是說ViewGroup本身就是一個(gè)View,但是它可以包含子View(當(dāng)然子View也可能是一個(gè)ViewGroup),所以不難理解,上面所展示的偽代碼表示的是ViewGroup 處理事件分發(fā)的流程。而View本身是不存在分發(fā),所以也沒有攔截方法(onInterceptTouchEvent),它只能在onTouchEvent方法中進(jìn)行處理消費(fèi)或者不消費(fèi)。
通過下面的流程圖,會(huì)更加清晰的幫助我們梳理事件分發(fā)機(jī)制
可以看出事件的傳遞過程都是從父View到子View。
子View可以通過requestDisallowInterceptTouchEvent方法干預(yù)父View的事件分發(fā)過程(ACTION_DOWN事件除外),而這就是我們處理滑動(dòng)沖突常用的關(guān)鍵方法;
對(duì)于View(注意!ViewGroup也是View)而言,如果設(shè)置了onTouchListener,那么OnTouchListener方法中的onTouch方法會(huì)被回調(diào)。onTouch方法返回true,則onTouchEvent方法不會(huì)被調(diào)用(onClick事件是在onTouchEvent中調(diào)用)所以三者優(yōu)先級(jí)是onTouch->onTouchEvent->onClick;
View 的onTouchEvent 方法默認(rèn)都會(huì)消費(fèi)掉事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false),View的longClickable默認(rèn)為false,clickable需要區(qū)分情況,如Button的clickable默認(rèn)為true,而TextView的clickable默認(rèn)為false;
事件傳遞的機(jī)制,這里給出一些總結(jié):
- 一個(gè)事件系列以down事件開始,中間包含數(shù)量不定的move事件,最終以u(píng)p事件結(jié)束;
- 正常情況下,一個(gè)事件序列只能由一個(gè)View攔截并消耗;
- 某個(gè)View攔截了事件后,該事件序列只能由它去處理,并且它的onInterceptTouchEvent不會(huì)再被調(diào)用;
- 某個(gè)View一旦開始處理事件,如果它不消耗ACTION_DOWN事件( onTouchEvnet返回false) ,那么同一事件序列中的其他事件都不會(huì)交給他處理,并且事件將重新交由他的父元素去處理,即父元素的onTouchEvent被調(diào)用;。
- 如果View不消耗ACTION_DOWN以外的其他事件,那么這個(gè)事件將會(huì)消失,此時(shí)父元素的onTouchEvent并不會(huì)被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終消失的點(diǎn)擊事件會(huì)傳遞給Activity去處理。
- ViewGroup默認(rèn)不攔截任何事件;
- View沒有onInterceptTouchEvent方法,一旦事件傳遞給它,它的onTouchEvent方法會(huì)被調(diào)用;
- View的onTouchEvent默認(rèn)消耗事件,除非他是不可點(diǎn)擊的( clickable和longClickable同時(shí)為false) 。View的longClickable屬性默認(rèn)false,clickable默認(rèn)屬性分情況(如TextView為false,button為true);
- View的enable屬性不影響onTouchEvent的默認(rèn)返回值;
- onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的,并且收到了down和up事件;
- 事件傳遞過程總是由外向內(nèi)的,即事件總是先傳遞給父元素,然后由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的分發(fā)過程,但是ACTION_DOWN事件除外;
三、滑動(dòng)沖突的解決方式
1、外部攔截法
所謂外部攔截法是指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,否則就不攔截。下面是偽代碼:
- public boolean onInterceptTouchEvent (MotionEvent event){
- boolean intercepted = false;
- int x = (int) event.getX();
- int y = (int) event.getY();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- intercepted = false;
- break;
- case MotionEvent.ACTION_MOVE:
- if (父容器需要當(dāng)前事件) {
- intercepted = true;
- } else {
- intercepted = false;
- }
- break;
- case MotionEvent.ACTION_UP:
- intercepted = false;
- break;
- default :
- break;
- }
- mLastXIntercept = x;
- mLastYIntercept = y;
- return intercepted;
針對(duì)不同沖突,只需修改父容器需要當(dāng)前事件的條件即可。其他不需修改也不能修改。
ACTION_DOWN:必須返回false。因?yàn)槿绻祷豻rue,后續(xù)事件都會(huì)被攔截,無法傳遞給子View;
ACTION_MOVE:根據(jù)需要決定是否攔截;
ACTION_UP:必須返回false。如果攔截,那么子View無法接受up事件,無法完成click操作。而如果是父容器需要該事件,那么在ACTION_MOVE時(shí)已經(jīng)進(jìn)行了攔截,根據(jù)上一節(jié)的結(jié)論3,ACTION_UP不會(huì)經(jīng)過onInterceptTouchEvent方法,直接交給父容器處理;
2、內(nèi)部攔截法
內(nèi)部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗,否則就交由父容器進(jìn)行處理。這種方法與Android事件分發(fā)機(jī)制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。下面是偽代碼:
- public boolean dispatchTouchEvent ( MotionEvent event ) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- switch (event.getAction) {
- case MotionEvent.ACTION_DOWN:
- parent.requestDisallowInterceptTouchEvent(true);
- break;
- case MotionEvent.ACTION_MOVE:
- int deltaX = x - mLastX;
- int deltaY = y - mLastY;
- if (父容器需要此類點(diǎn)擊事件) {
- parent.requestDisallowInterceptTouchEvent(false);
- }
- break;
- case MotionEvent.ACTION_UP:
- break;
- default :
- break;
- }
- mLastX = x;
- mLastY = y;
- return super.dispatchTouchEvent(event);
- }
除了子元素需要做處理外,父元素也要默認(rèn)攔截除了ACTION_DOWN以外的其他事件,這樣當(dāng)子元素調(diào)用parent.requestDisallowInterceptTouchEvent(false)方法時(shí),父元素才能繼續(xù)攔截所需的事件。因此,父元素要做以下修改:
- public boolean onInterceptTouchEvent (MotionEvent event) {
- int action = event.getAction();
- if(action == MotionEvent.ACTION_DOWN) {
- return false;
- } else {
- return true;
- }
- }
四、事件分發(fā)機(jī)制-源碼分析
從源碼的角度來了解一下Android下的事件分發(fā)機(jī)制
1、首先看到Activity中的dispatchTouchEvent()方法源碼:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- // 如果是ACTION_DOWN事件會(huì)走這個(gè)語句,onUserInteraction()這個(gè)方法在系統(tǒng)中是空實(shí)現(xiàn)
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- onUserInteraction();
- }
- /**
- * 主要看一下這行代碼
- * getWindow()表示獲取Window的子類PhoneWindow對(duì)象
- * 也就是說調(diào)用PhoneWindow中的superDispatchTouchEvent(ev)方法,判斷是否有控件處理事件
- */
- if (getWindow().superDispatchTouchEvent(ev)) {
- return true;
- }
- // 如果沒有控件能處理事件,就走這一行代碼,調(diào)用Activity的onTouchEvent()方法處理事件
- return onTouchEvent(ev);
- }
2、接著進(jìn)入到PhoneWindow中的,查看superDispatchTouchEvent(ev)這個(gè)方法:
- @Override
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return mDecor.superDispatchTouchEvent(event);
- }
在PhoneWindow類的superDispatchTouchEvent(ev)方法中,直接調(diào)用了mDecor對(duì)象的superDispatchTouchEvent(ev)方法,mDecore其實(shí)就是繼承至FrameLayout的DecorView的對(duì)象。在《Activity的組成》這篇博客中貼出了DecorView類的定義源碼。
接著查看類中的superDispatchTouchEvent(ev)這個(gè)方法:
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return super.dispatchTouchEvent(event);
- }
只有一句代碼,super.dispatchTouchEvent(event),調(diào)用父類的dispatchTouchEvent(event)方法,也就是FrameLayout的dispatchTouchEvent(event)方法,查看FrameLayout類會(huì)發(fā)現(xiàn)FrameLayout并沒有重寫dispatchTouchEvent(event)方法,那么就是使用的ViewGroup中的dispatchTouchEvent(event)方法。
到這里也就完全說明了Activity在做事件分發(fā)時(shí)調(diào)用的是ViewGroup中的dispatchTouchEvent()方法。
3、查看ViewGroup中的dispatchTouchEvent()方法:
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- ...
- boolean handled = false;
- // 過濾觸摸安全策略,如果是false(窗口或控件被遮住了時(shí)),直接跳出觸摸事件
- // 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法),則返回True;如果應(yīng)該刪除事件,則返回false
- if (onFilterTouchEventForSecurity(ev)) {
- ...
- /**
- * 如果是DOWN事件就先將mFirstTouchTarget設(shè)置為null,
- * 然后在resetTouchState()方法中重置狀態(tài)
- */
- if (actionMasked == MotionEvent.ACTION_DOWN) {
- cancelAndClearTouchTargets(ev);
- resetTouchState();
- }
- // 定義變量intercepted標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
- final boolean intercepted;
- // 事件為ACTION_DOWN或者mFirstTouchTarget不為null(有控件消費(fèi)touch事件)
- if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
- //判斷disallowIntercept(禁止攔截)標(biāo)志位
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- //當(dāng)沒有禁止攔截時(shí)
- if (!disallowIntercept) {
- // 調(diào)用onInterceptTouchEvent(ev)方法,并將返回值賦給intercepted
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action);
- } else {
- //當(dāng)禁止攔截時(shí),指定intercepted = false,表示不攔截事件
- intercepted = false;
- }
- } else {
- //當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(沒有控件消費(fèi)touch事件)時(shí)
- //設(shè)置 intercepted = true,表示ViewGroup執(zhí)行Touch事件攔截的操作。
- intercepted = true;
- }
- ...
- // 事件分發(fā)
- final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
- TouchTarget newTouchTarget = null;
- boolean alreadyDispatchedToNewTouchTarget = false;
- //不是ACTION_CANCEL事件并且intercepted為false(ViewGroup不攔截事件onInterceptTouchEvent()方法返回false)
- if (!canceled && !intercepted) {
- //處理ACTION_DOWN事件
- if (actionMasked == MotionEvent.ACTION_DOWN
- || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- final int actionIndex = ev.getActionIndex();
- final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;
- removePointersFromTouchTargets(idBitsToAssign);
- final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
- // 依據(jù)Touch坐標(biāo)尋找孩子控件來消費(fèi)Touch事件
- final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
- final boolean customOrder = isChildrenDrawingOrderEnabled();
- // 遍歷所有孩子控件,判斷哪個(gè)消費(fèi)Touch事件
- for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
- if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
- continue;
- }
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- // 找到消費(fèi)Touch事件的孩子控件,跳出循環(huán),并用newTouchTarget表示孩子控件
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- break;
- }
- resetCancelNextUpFlag(child);
- // 沒有跳出循環(huán),走到這一步,就會(huì)調(diào)用dispatchTransformedTouchEvent()方法,將事件傳給孩子控件做遞歸處理,第三個(gè)參數(shù)不為null
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- ...
- }
- }
- }
- /**
- * 如果在循環(huán)中沒有孩子控件消費(fèi)事件并且之前的mFirstTouchTarget不為空
- */
- if (newTouchTarget == null && mFirstTouchTarget != null) {
- // 將mFirstTouchTarget的賦給newTouchTarget
- newTouchTarget = mFirstTouchTarget;
- while (newTouchTarget.next != null) {
- newTouchTarget = newTouchTarget.next;
- }
- // newTouchTarget指向了最初的TouchTarget
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- }
- }
- }
- /**
- * 分發(fā)Touch事件至目標(biāo)控件(target),以上過程主要針對(duì)ACTION_DOWN,
- * 如果不是(上一步中判斷intercepted變量),比如ACTION_MOVE和ACTION_UP,就是從此處開始執(zhí)行
- */
- if (mFirstTouchTarget == null) {
- /**
- * mFirstTouchTarget為null表示Touch事件未被消費(fèi)或Touch事件被攔截了,
- * 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法,遞歸處理,第三個(gè)參數(shù)為null
- */
- handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
- } else {
- /**
- * mFirstTouchTarget不為null表示找到了可以消費(fèi)Touch事件的子View
- * 并且MOVE或UP事件可以傳遞到該子View
- */
- TouchTarget predecessor = null;
- // 將找到的可以消費(fèi)事件的mFirstTouchTarget賦給目標(biāo)控件(target)
- TouchTarget target = mFirstTouchTarget;
- while (target != null) {
- final TouchTarget next = target.next;
- // 如果已經(jīng)分發(fā)到新的控件并且消費(fèi)事件的目標(biāo)控件就是新的控件
- if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
- handled = true;
- } else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- // 否則調(diào)用dispatchTransformedTouchEvent()方法進(jìn)行遞歸處理,第三個(gè)參數(shù)不為null
- if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
- handled = true;
- }
- ...
- }
- predecessor = target;
- target = next;
- }
- }
- /**
- * 如果是ACTION_UP和ACTION_CANCEL事件,還原狀態(tài)
- */
- if (canceled|| actionMasked == MotionEvent.ACTION_UP
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- resetTouchState();
- } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
- ...
- }
- }
- ...
- return handled;
- }
我們可以看到在上面的方法中,調(diào)用的onInterceptTouchEvent()判斷是否需要攔截事件。
查看ViewGroup中的dispatchTransformedTouchEvent()方法:
- private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
- View child, int desiredPointerIdBits) {
- final boolean handled;
- ...
- if (child == null) {
- // 如果孩子控件為空,就調(diào)用View的dispatchTouchEvent()方法,在View的dispatchTouchEvent()方法中會(huì)調(diào)用onTouchEvent()方法
- handled = super.dispatchTouchEvent(event);
- } else {
- // 如果孩子控件不為空,就調(diào)用孩子控件的dispatchTouchEvent()方法
- // 在此處孩子控件也還有可能是ViewGroup,所以就是繼續(xù)調(diào)用ViewGroup的dispatchTouchEvent()方法
- handled = child.dispatchTouchEvent(event);
- }
- event.setAction(oldAction);
- return handled;
- }
- ...
- transformedEvent.recycle();
- return handled;
- }
- 查看ViewGroup中onInterceptTouchEvent()方法:
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return false;
- }
在ViewGroup中,沒有重寫onTouchEvent()方法,所以調(diào)用的是View中的onTouchEvent()方法。
4、在View類中,首先看一下View中的dispatchTouchEvent()方法:
- public boolean dispatchTouchEvent(MotionEvent event) {
- // If the event should be handled by accessibility focus first.
- if (event.isTargetAccessibilityFocus()) {
- // We don't have focus or no virtual descendant has it, do not handle the event.
- if (!isAccessibilityFocusedViewOrHost()) {
- return false;
- }
- // We have focus and got the event, then use normal event dispatch.
- event.setTargetAccessibilityFocus(false);
- }
- boolean result = false;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onTouchEvent(event, 0);
- }
- // 如果是DOWN事件,重置狀態(tài)
- final int actionMasked = event.getActionMasked();
- if (actionMasked == MotionEvent.ACTION_DOWN) {
- stopNestedScroll();
- }
- // 過濾觸摸安全策略,如果是false(窗口或控件被遮住了時(shí)),直接跳出觸摸事件
- // 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法),則返回True;如果應(yīng)該刪除事件,則返回false
- if (onFilterTouchEventForSecurity(event)) {
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null
- && (mViewFlags & ENABLED_MASK) == ENABLED
- && li.mOnTouchListener.onTouch(this, event)) {
- // 當(dāng)前控件是可用(enabled)的并且View調(diào)用了setOnTouchListener()方法且返回了true,那么就設(shè)置result為true
- result = true;
- }
- // result為false,表示沒有調(diào)用setOnTouchListener()方法或該方法返回false,那么就調(diào)用
- // View的onTouchEvent()方法
- if (!result && onTouchEvent(event)) {
- result = true;
- }
- }
- if (!result && mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
- }
- // 如果是UP事件或CANCEL事件或者是DOWN事件但是該控件不能消費(fèi)事件時(shí),重置狀態(tài)
- if (actionMasked == MotionEvent.ACTION_UP ||
- actionMasked == MotionEvent.ACTION_CANCEL ||
- (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
- stopNestedScroll();
- }
- return result;
- }
最后查看View的onTouchEvent()方法:
- public boolean onTouchEvent(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- final int viewFlags = mViewFlags;
- final int action = event.getAction();
- // 判斷是否有單擊或長按事件
- final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
- || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
- || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
- setPressed(false);
- }
- mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
- // 控件disabled 了,他還能消耗觸摸事件,只是不相應(yīng)她了
- return clickable;
- }
- // 如果有代理,調(diào)用代理的方法
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
- // 對(duì)點(diǎn)擊事件的具體處理,只要有點(diǎn)擊事件,那么onTouchEvent()方法就返回了 true
- if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
- switch (action) {
- case MotionEvent.ACTION_UP:
- // ...
- // mHasPerformedLongPress 表示長按事件的返回值,如果長按事件的的回調(diào)方法返回了true,那么在同一事件序列中,點(diǎn)擊事件就不會(huì)調(diào)用了(否則會(huì)同時(shí)相應(yīng)長按事件和點(diǎn)擊事件)
- if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
- // This is a tap, so remove the longpress check
- removeLongPressCallback();
- // Only perform take click actions if we were in the pressed state
- if (!focusTaken) {
- // Use a Runnable and post this rather than calling
- // performClick directly. This lets other visual state
- // of the view update before click actions start.
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
- if (!post(mPerformClick)) {
- performClickInternal(); // 會(huì)調(diào)用 performClick()方法處理單擊事件
- }
- }
- }
- if (mUnsetPressedState == null) {
- mUnsetPressedState = new UnsetPressedState();
- }
- if (prepressed) {
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- mIgnoreNextUpEvent = false;
- break;
- case MotionEvent.ACTION_DOWN:
- if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
- mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
- }
- mHasPerformedLongPress = false;
- if (!clickable) {
- checkForLongClick(0, x, y);
- break;
- }
- if (performButtonActionOnTouchDown(event)) {
- break;
- }
- // Walk up the hierarchy to determine if we're inside a scrolling container.
- boolean isInScrollingContainer = isInScrollingContainer();
- // 根據(jù)是否在滾動(dòng)容器中,使用不同方式調(diào)用長按事件的回調(diào)
- if (isInScrollingContainer) {
- mPrivateFlags |= PFLAG_PREPRESSED;
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap(); // 最終調(diào)用長按事件回調(diào)
- }
- mPendingCheckForTap.x = event.getX();
- mPendingCheckForTap.y = event.getY();
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- } else {
- // Not inside a scrolling container, so show the feedback right away
- setPressed(true, x, y);
- checkForLongClick(0, x, y); // 最終調(diào)用長按事件回調(diào)
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- // ...
- break;
- }
- // 只要有點(diǎn)擊事件,那么onTouchEvent()方法就返回了 true
- return true;
- }
- return false;
- }
從上面的代碼來看,只要View的 CLICLABLE和LONG_CLICKABLE有一個(gè)為true,那么onTouchEvent()方法就會(huì)返回true,而且不管該View是否為DISABLE狀態(tài),其他情況返回false。true表示該控件可以消費(fèi)事件,false表示該控件不能消費(fèi)事件?,F(xiàn)在,在回過頭來看一下第一張圖,是不是更加的清晰了呢。
5、最后,對(duì)于View 的 CLICKABLE 和 LONG_CLICKABLE默認(rèn)值,LONG_CLICKABLE默認(rèn)值為false,但是對(duì)于CLICKABLE就要根據(jù)具體的View來看了,確切的說是可點(diǎn)擊的View的CLICKABLE值為true,如Button,不可點(diǎn)擊的View的CLICKABLE值為false,比如TextView,但是當(dāng)我們調(diào)用了View的 setOnClickListener(@Nullable OnClickListener l) 方法或者 setOnLongClickListener(@Nullable OnLongClickListener l) 方法就會(huì)將對(duì)應(yīng)的值改為true。
- public void setOnClickListener(@Nullable OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- getListenerInfo().mOnClickListener = l;
- }
- public void setOnLongClickListener(@Nullable OnLongClickListener l) {
- if (!isLongClickable()) {
- setLongClickable(true);
- }
- getListenerInfo().mOnLongClickListener = l;
- }
總結(jié)
- 對(duì)于ViewGroup和View的disatchTouchEvent()和onTouchEvent()方法,return true表示處理事件,事件終結(jié);return false表示不處理事件,讓事件回傳到上一層的onTouchEvent()方法中,也就是父控件中的onTouchEvent()方法中;
- 對(duì)于Activity的disatchTouchEvent()方法,如果沒有重寫,就會(huì)通過調(diào)用ViewGroup的disatchTouchEvent()方法開始分發(fā)事件,如果重寫了,那么不管返回true還是false都會(huì)消費(fèi)事件,不在將事件往下分發(fā);
- 對(duì)于dispatchTouchEvent()方法,如果開發(fā)者不重寫,就會(huì)走系統(tǒng)中的默認(rèn)實(shí)現(xiàn),在ViewGroup中會(huì)調(diào)用ViewGroup的onInterceptTouchEvent()方法,而在View中會(huì)直接把事件分發(fā)給View的onTouchEvent()方法處理;
- 對(duì)于ViewGroup而言,如果ViewGroup要自己處理事件,需要重寫onInterceptTouchEvent()方法并且返回true,這樣才會(huì)終止事件的傳遞,并調(diào)用ViewGroup的onTouchEvent()方法處理事件,否則調(diào)用系統(tǒng)onInterceptTouchEvent()方法,系統(tǒng)默認(rèn)的返回值是false,不處理事件將事件傳遞下去;
- 對(duì)于View而言,在View中是沒有onInterceptTouchEvent()方法的,因?yàn)樗麤]有孩子控件,不需要攔截,系統(tǒng)在dispatchTouchEvent()方法中默認(rèn)會(huì)把事件分發(fā)給View的onTouchEvent()方法處理;
- 在Android中,最開始獲取到事件的是Activity,然后由Activity的dispatchTouchEvent()方法開始分發(fā)事件;
- 如果一個(gè)事件由Activity開始下發(fā),但是所有的控件都不處理事件,最終就會(huì)回到Activity的onTouchEvent()方法,如果Activity也不消費(fèi)事件,那么這個(gè)事件就丟失了。






