1. 程式人生 > >Android開發——事件分發機制詳解---微信魚蝦蟹源碼搭建

Android開發——事件分發機制詳解---微信魚蝦蟹源碼搭建

lai reset 微信 影響 ren 事件分發機制 lis forum hlist

轉載請註明出處:http://h5.hxforum.com
深入學習事件分發機制,是為了解決在Android開發中遇到的滑動沖突問題做準備。事件分發機制描述了用戶的手勢一系列事件是如何被Android系統傳遞並消費的。

首先對事件分發機制進行概述:如果當一個點擊事件發生時,事件最先傳遞給當前Activity,再傳遞給Window,接著傳遞給頂級View,最後按照事件分發機制去分發事件。事件的傳遞過程可以用以下偽代碼進行描述:微信魚蝦蟹源碼搭建(h5.hxforum.com) 聯系方式170618633533企鵝2952777280 微信Tel17061863533源碼出售 房卡出售 後臺出租有意者私聊
[java] view plain copy

public boolean dispatchTouchEvent(MotionEvent ev){//事件傳遞到,那麽該方法一定會被調用
boolean consume = false;
//onInterceptTouchEvent只存在於ViewGroup,判斷是否攔截該事件
//但不是每次都調用該方法,後面會詳細介紹
if(onInterceptTouchEvent(ev)){
if(!OnTouchListener.onTouch(ev)){// OnTouchListener優先級較onTouchEvent高
consume = onTouchEvent(ev);//處理點擊事件
}
}else{
consume = child. dispatchTouchEvent(ev);
}
return consume;
}
對於事件傳遞後的事件消費,如果一個View設置了OnTouchListener,則OnTouchListener的onTouch會首先被調用。若onTouch返回false,最後才輪到onTouchEvent去消費該事件(我們平時設置的OnclickListener就在onTouchEvent方法中,優先級較低)。
若onTouchEvent返回了true,則表示事件已經被消費了,否則它的父容器的onTouchEvent將會調用,以此類推,直至由Activity的onTouchEvent被調用。
整個過程的簡要流程圖如下所示:

  1. 事件的分發詳解
    如果需要再進一步分析事件的分發機制,那麽必須閱讀源碼。
    一個點擊事件發生時,事件第一步最先傳遞給當前Activity。

  2. 1 Activity對事件的分發
    [java] view plain copy
    public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
    }
    return onTouchEvent(ev);
    }
    從源碼裏可以看出,事件交給了Activity所附屬的Window進行分發,返回true則結束事件分發,否則代表所有的View的onTouchEvent返回了false(均不處理),這時是由Activity的onTouchEvent來處理。

  3. 2 Window對事件的分發
    Window的superDispatchTouchEvent()是一個抽象方法,Window的唯一實現是PhoneWindow。
    下面代碼便來自於PhoneWindow對事件的分發邏輯。
    [java] view plain copy
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
    }
    從源碼裏可以看出,事件交給了DecorView處理。我們繼續查看DecorView的定義。

  4. 3 DecorView的定義
    [java] view plain copy
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{…}

public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
從源碼裏可以看出,DecorView是繼承自FrameLayout的,毫無疑問FrameLayout又繼承了ViewGroup,那麽剩下的工作就是研讀 GroupView的dispatchTouchEvent方法了。

  1. 4 ViewGroup
    ViewGroup和Activity、View比,多了一個onInterceptTouchEvent()事件攔截方法,事件傳遞到ViewGroup若onInterceptTouchEvent返回true,則事件由ViewGroup處理(OnTouchListener比onTouchEvent優先級要高,這一點前面也介紹過了)。若返回false,子View的dispatchTouchEvent會被調用。
    但是onInterceptTouchEvent並不是每次都會調用並判斷是否攔截事件,ViewGroup.dispatchTouchEvent()的源碼分析如下:
    [java] view plain copy
    // 檢查是否進行事件攔截
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
    //回調onInterceptTouchEvent(),返回false表示不攔截touch,否則攔截touch事件
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action);
    } else {
    intercepted = false;
    }
    } else {
    //沒有touch事件的傳遞對象,同時該動作不是初始動作down,ViewGroup繼續攔截事件
    intercepted = true;
    }
    從源碼裏可以看出,ViewGroup的onInterceptTouchEvent判斷是否去攔截事件的前提是ACTION_DOW或者mFirstTouchTarget != null,關於mFirstTouchTarget,如果事件由ViewGroup的子View成功處理,mFirstTouchTarget會指向該子View不為空。
    當面對ACTION_DOW事件時,ViewGroup總是會調用自己的onInterceptTouchEvent來詢問是否去攔截事件,因此若ViewGroup攔截了ACTION_DOWN事件,mFirstTouchTarget一定為空。當後續ACTION_MOVE和ACTION_UP事件到來時,ViewGroup的onInterceptTouchEvent不會調用,直接攔截。那麽有什麽辦法可以修改這種默認機制呢?

我們還註意到源碼中的標記位FLAG_DISALLOW_INTERCEPT,該標記位通過子View的getParent().requestDisallowInterceptTouchEvent方法來設置,作用是ViewGroup將無法攔截除ACTION_DOW以外的點擊事件。這為我們後面如何處理滑動沖突提供思路。至於為什麽無法攔截ACTION_DOW,可以由以下源碼證明。
[java] view plain copy
//處理初始的down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN到來時的重置操作
//當app切換、 ANR或一些其他的touch狀態發生時,framework會丟棄或取消先前的touch狀態
cancelAndClearTouchTargets(ev);
resetTouchState();//該方法中會重置FLAG_DISALLOW_INTERCEPT標記位
}

  1. 5 View對事件的處理
    當ViewGroup的onInterceptTouchEvent返回false,會首先遍歷所有的子元素,判斷子元素是否能夠接收點擊事件(通過判斷子元素是否在播放動畫並且點擊坐標落在該子元素區域內)。若子元素具備接收事件的條件,那麽它的dispatchTouchEvent會被調用,若遍歷完所有的子元素均返回false,那麽只能ViewGroup自己去處理該事件。子元素的該方法返回true會終止遍歷子元素。
    事件傳遞就來到了子View的“手裏”,處理過程如下。
    [java] view plain copy
    public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    //...
    if (onFilterTouchEventForSecurity(event)) {
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
    && li.mOnTouchListener.onTouch(this, event)) {
    return true; }

    if (onTouchEvent(event)) {
    return true; }
    }
    //…
    return result;
    }
    View無法繼續向下傳遞事件,只能處理之。從源碼第8行可以看出會執行View的OnTouchListener.onTouch這個函數,若返回true,onTouchEvent便不會再被調用了。可見OnTouchListener比onTouchEvent優先級更高。

  2. 6 View的onTouchEvent
    首先當View處於不可用狀態時的處理過程如下:
    [java] view plain copy
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
    // A disabled view that is clickable still consumes the touch
    // events, it just doesn‘t respond to them.
    return (((viewFlags & CLICKABLE) == CLICKABLE ||
    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    可以看出View的enable屬性不影響onTouchEvent的返回值,View的onTouchEvent會默認消耗事件並返回true,除非其CLICKABLE和LONG_CLICKABLE均為false。後者在View中默認為false,前者根據控件本身來決定。通過setClickable和setLongClickable可以改變View的這兩個屬性。setOnClickListener和setOnLongClickListener本質上也是通過setClickable和setLongClickable來改變View的這兩個屬性。

View對事件的具體處理如下:
[java] view plain copy
public boolean onTouchEvent(MotionEvent event) {
//...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
performClick();
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
//
return true;
}
return false;
}
從源碼中可以看出,如果一個控件是clickable或longclickable的,那麽就會執行ACTION_UP、ACTION_DOWN等case裏面,並最終返回true。需要說明的是,如果在上一個case(比如:ACTION_UP)中返回了false,那麽下面所有的case(比如:ACTION_CANCEL、ACTION_MOVE等)都不會得到執行。

ACTION_UP發生時會調用performClick方法,源碼如下所示:
[java] view plain copy
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

Android開發——事件分發機制詳解---微信魚蝦蟹源碼搭建