android事件分發機制詳解

先講View:
只要你觸摸到了任何一個控件,就一定會調用該控件的dispatchTouchEvent方法,看下該函數的實現:
public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;
    }
    return onTouchEvent(event);  
}  
可以看出一個Enable的控件設置了OnTouchListener,如果在onTouch()裏面返回了true,那麼觸碰事件就被onTouch()“消費”掉了,不會走到onTouchEvent();如果返回false,那麼就會往下走,這樣到了onTouchEvent()方法裏面,其實現如下:
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的,那麼就會走到ACTION_UP,ACTION_DOWN等case裏面,並且最終返回true。需要說明的是:如果在上一個case(比如:ACTION_UP)返回了false,那麼其下面所有的case(比如:ACTION_CANCEL、ACTION_MOVE等)都不會得到執行。在ACTION_UP下面調用了performClick()方法,其實現如下:
public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  
在這裏我們就知道了爲什麼點擊一個按鈕會執行onClick()方法。
總結一下,總的調用順序:
dispatchTouchEvent-->onTouch()[需要返回false纔會繼續往下]-->onTouchEvent()[需要控件是clickable纔會繼續往下走]-->onClick()。

再看看ViewGroup的事件分發,ViewGroup是什麼呢?
ViewGroup就是一組View的集合,它包含很多的子View和子VewGroup,是Android中所有佈局的父類或間接父類,像LinearLayout、RelativeLayout等都是繼承自ViewGroup的。但ViewGroup實際上也是一個View,只不過比起View,它多了可以包含子View和定義佈局參數的功能。
面前說點擊某一個控件,會調用到該控件的dispatchTouchEvent方法,實際是這樣的:當你點擊了某個控件,首先會去調用該控件所在佈局的dispatchTouchEvent方法,然後在佈局的dispatchTouchEvent方法中找到被點擊的相應控件,再去調用該控件的dispatchTouchEvent方法。
下面來看下ViewGroup的dispatchTouchEvent()方法的實現吧:
public boolean dispatchTouchEvent(MotionEvent ev) {
	...    
	if (disallowIntercept || !onInterceptTouchEvent(ev)) {		
		final int count = mChildrenCount;
		for (int i = count - 1; i >= 0; i--) {
			final View child = children[i];
			...
			if (child.dispatchTouchEvent(ev))  {
				mMotionTarget = child;
				return true;
			}
		}
	}
    ...
    if (target == null) {
        ...
        return super.dispatchTouchEvent(ev);
    }
    ...
    return target.dispatchTouchEvent(ev);
}
可以看到裏面調用了一個函數onInterceptTouchEvent(),這個是什麼東東呢?看下實現:
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
} 
該函數就是說,父視圖是否攔截子視圖的touch事件,返回false,說明默認是不攔截的。由於此處返回是的false,所以會走到子view的dispatchTouchEvent()方法,這個方法上面我們詳細分析過了。如果點擊的是父視圖的空白區域,或是onInterceptTouchEvent返回true,那麼就會走到第17行,這裏的super當然就View啦,就又回到了上面我們講過的View的分發流程。
看一個例子:
public class MyLayout extends LinearLayout {  
  
    public MyLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
	
	@Override  
    public boolean onInterceptTouchEvent(MotionEvent ev) {  
        return true;  
    }   
}  
myLayout.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        Log.d("TAG", "myLayout on touch");  
        return false;  
    }  
});  
button1.setOnClickListener(new OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        Log.d("TAG", "You clicked button1");  
    }  
});  

該例子中,MyLayout佈局裏面包含了一個button1,並且爲myLayout和button1都添加了OnTouchListener,如果我們重寫的onInterceptTouchEvent返回true,那麼touch事件就被myLayout攔截了,不會傳到button1上面,反之會則會傳到button1上面,當然點擊空白區域還是會被myLayout捕獲的。

下面用一張圖來總結下這個流程:


在解決事件衝突中的運用:

比如scrollview嵌套viewpager導致的滑動衝突
有兩種解決方法:
1、重寫scrollview類的onInterceptTouchEvent方法,如果發現是水平滑動的話,那就在該方法內返回false,這樣就不攔截viewpager了,會執行viewpager的onTouchEvent方法;
2、重寫viewpager中的onTouchEvent方法,如果發現水平滑動就設置getParent().requestDisallowInterceptTouchEvent(true)來通知父控件不要攔截事件,如果是豎直滑動就設置爲false。

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