Android事件分發

public boolean dispatchTouchEvent(MotionEvent ev){
  boolean result =false;
   if(onInterceptTouchEvent(ev)){
    result=onTouchEvent(ev);
    }
   else {
        result=child.dispatchTouchEvent(ev);
        }
    return result;
}

1.dispatchTouchEvent是進行事件分發;

2.onInterceptTouchEvent是攔截事件  ,攔截的意思是,如果攔截了,就不往子控件走,不會執行子控件的onTouchEvent。

3.onTouchEvent是消費點擊事件

 

如果onInterceptTouchEvent不攔截,那麼久看子控件的onInterceptTouchEvent是否會攔截並且onTouchEvent會調用,如果子控件onTouchEvent調用,那就沒有父控件的onTouchEvent什麼事,

 

 

僅針對ACTION_DOWN事件的傳遞

 

 

dispatchTouchEvent 和 onTouchEvent   (向上)

return true,終結事件傳遞;

return false,事件都回傳給父控件的onTouchEvent處理

 

dispatchTouchEvent 返回值爲 false,意味着事件停止往子View分發,並往父控件回溯。

onTouchEvent 返回值爲 false,意味着不消費事件,並往父控件回溯。

 

onInterceptTouchEvent返回false,意味着繼續往子控件分發,

onInterceptTouchEvent返回ture,意味着攔截事件          不往子控件分發。

 

 

控件被點擊時,

onTouch返回false—>dispatchTouchEvent方法返回false—>執行onTouchEvent—>在performClick方法裏回調onClick

onTouch返回true—>dispatchTouchEvent方法返回true—>不執行onTouchEvent,顯然onClick方法也不會被調用

 

 

點擊事件的攔截機制,其實就是一個U型的模型。dispatchTouchEvent 和 onTouchEvent(如果執行到的話)爲false回溯父控件。onInterceptTouchEvent爲false分發給子控件。

 

 

onTouch的優先級高於onClick

控件被點擊時,

onTouch返回false—>dispatchTouchEvent方法返回false—>執行onTouchEvent—>在performClick方法裏回調onClick

onTouch返回true—>dispatchTouchEvent方法返回true—>不執行onTouchEvent,顯然onClick方法也不會被調用

進階
ACTION_MOVE和ACTION_UP相關
先來看看兩個實驗:

在View的dispatchTouchEvent 返回false並且在ViewGroup的onTouchEvent返回true
紅色的箭頭代表ACTION_DOWN事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP事件的流向


ViewDispatch_2
在ViewGroup 的onTouchEvent 返回true
紅色的箭頭代表ACTION_DOWN 事件的流向
藍色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向


ViewDispatch_03
總結一下:

如果在某個控件的dispatchTouchEvent 返回true消費終結事件,那麼收到ACTION_DOWN 的函數也能收到ACTION_MOVE和ACTION_UP。

在哪個View的onTouchEvent 返回true,那麼ACTION_MOVE和ACTION_UP的事件從上往下傳到這個View後就不再往下傳遞了,而直接傳給自己的onTouchEvent 並結束本次事件傳遞過程。

ACTION_DOWN事件在哪個控件消費了(return true), 那麼ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發往下傳,就只會傳到這個控件,不會繼續往下傳

如果ACTION_DOWN事件是在dispatchTouchEvent消費,那麼事件到此爲止停止傳遞

如果ACTION_DOWN事件是在onTouchEvent消費的,那麼會把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理並結束傳遞。

onTouch()和onTouchEvent()的區別
兩個方法都是在View的dispatchTouchEvent中調用,但onTouch優先於onTouchEvent執行。

如果在onTouch方法中返回true將事件消費掉,onTouchEvent將不會再執行。

View的dispatchTouchEvent方法中:

if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
    result = true;
}

if (!result && onTouchEvent(event)) {
    result = true;
}
onTouch能夠執行需要的兩個前提:

mOnTouchListener不爲空
當前點擊的控件必須是ENABLED
因此如果你有一個控件是非enable的,那麼給它註冊onTouch事件將不會執行。

應用場景—滑動衝突的解決
滑動衝突在Android開發中一直都是一個痛點,之前的所有講解,就像是所有的招式,滑動衝突,就是我們的用武之地。

常見滑動衝突場景
外部滑動和內部滑動方向不一致

ViewPager和Fragment配合使用組成的頁面滑動效果。這種衝突的解決方式,一般都是根據水平滑動還是豎直滑動(滑動的距離差)來判斷到底是由誰來攔截事件。

外部滑動和內部滑動方向一致

內外兩層同時能上下滑動或者能同時左右滑動。這種一般都是根據業務來進行區分。

以上兩種場景的嵌套

滑動衝突的解決方式
外部攔截法

外部攔截法,就是所有事件都先經過父容器的攔截處理,由父容器來決定是否攔截。這種方式需要重寫父容器的onInterceptTouchEvent方法,僞代碼如下:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if (父容器需要當前點擊事件) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted=false;
            break;
        default:
            break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}
幾點說明:

不攔截ACTION_DOWN事件。一旦父容器攔截ACTION_DOWN,則後續的ACTION_MOVE和ACTION_UP事件都會直接交由父容器處理,無法傳遞給子元素。
ACTION_MOVE事件根據具體需求來決定是否攔截。
ACTION_UP事件必須返回false,ACTION_UP事件本身沒什麼意義,但如果父容器在ACTION_UP返回true會導致子元素無法接收ACTION_UP事件,無法響應onClick事件。
內部攔截法

內部攔截法是指父容器不攔截任何事件,所有事件都傳遞給子元素。內部攔截法需要配合requestDisallowInterceptTouchEvent方法才能正常工作。這種方式需要重寫子元素的dispatchTouchEvent方法,僞代碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (父容器需要當前點擊事件) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(ev);
}
父元素需要默認攔截除ACTION_DOWN事件以外的其他事件,父元素修改如下:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction()==MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}
ACTION_DOWN事件並不受FLAG_DISALLOW_INTERCEPT這個標記位的控制。一旦父容器攔截ACTION_DOWN事件,那麼所有的事件都無法傳遞到子元素中去。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章