關於view事件體系的幾個問題
view和viewGroup事件區別
這裡主要說下在事件分發上的區別,繪製上面也是有區別的,我們都看過相關原始碼。
這裡總結一下:
①事件分發
事件分發機制主要有三個方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()
1.ViewGroup包含這三個方法,而View則只包含dispatchTouchEvent()、onTouchEvent()兩個方法,不包含onInterceptTouchEvent()。
2.觸控事件由Action_Down、Action_Move、Action_Up組成,一次完整的觸控事件,包含一個Down和Up,以及若干個Move(可以為0);
3.在Action_Down的情況下,事件會先傳遞到最頂層的ViewGroup,呼叫ViewGroup的dispatchTouchEvent(),①如果ViewGroup的onInterceptTouchEvent()返回false不攔截該事件,則會分發給子View,呼叫子View的dispatchTouchEvent(),如果子View的dispatchTouchEvent()返回true,則呼叫View的onTouchEvent()消費事件。②如果ViewGroup的onInterceptTouchEvent()返回true攔截該事件,則呼叫ViewGroup的onTouchEvent()消費事件,接下來的Move和Up事件將由該ViewGroup直接進行處理。
4.當某個子View的dispatchTouchEvent()返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下來的Move和Up事件將由該子View直接進行處理。
5.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch();觸發的方式是呼叫super.dispatchTouchEvent函式,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
6..由於子View是儲存在ViewGroup中的,多層ViewGroup的節點結構時,上層ViewGroup儲存的會是真實處理事件的View所在的ViewGroup物件。如ViewGroup0——ViewGroup1——TextView的結構中,TextView返回了true,它將被儲存在ViewGroup1中,而ViewGroup1也會返回true,將被儲存在ViewGroup0中;當Move和Up事件來時,會先從ViewGroup0傳遞到ViewGroup1,再由ViewGroup1傳遞到TextView,最後事件由TextView消費掉。
7.子View可以調getParent().requestDisallowInterceptTouchEvent(),請求父ViewGroup不攔截事件
②繪製原理
UI繪製主要有五個方法:onDraw(),onLayout(),onMeasure(),dispatchDraw(),drawChild()
1.ViewGroup包含這五個方法,而View只包含onDraw(),onLayout(),onMeasure()三個方法,不包含dispatchDraw(),drawChild()。
2.繪製流程:onMeasure(測量)——>onLayout(佈局)——>onDraw(繪製)。
3.onLayout():對於View來說,onLayout()只是一個空實現;而對於ViewGroup來說,onLayout()使用了關鍵字abstract的修飾,要求其子類必須過載該方法,目的就是安排其children在父檢視的具體位置。
4.draw過程:drawBackground()繪製背景——>onDraw()對View的內容進行繪製——>dispatchDraw()對當前View的所有子View進行繪製——>onDrawScrollBars()對View的滾動條進行繪製。
viewpager和listview的事件衝突
滑動衝突的解決方案:
1.外部攔截法
具體做法就是當你不想把事件傳遞給子控制元件的時候在onInterceptTouchEvent方法中返回true即可攔截事件,這時候子控制元件將不會再接收到這一次的touch事件流(所謂touch事件流是以ACTION_DOWN開始,中間包含若干個ACTION_MOVE,以ACTION_UP結束的一連串事件)。虛擬碼如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if ( condition ) {
return true;
}
return false;
}
condition就得根據具體情形來設定。
2.內部攔截法
首先,我們讓父控制元件攔截除了ACTION_DOWN以外的所有事件,如果連ACTION_DOWN都攔截那麼子控制元件將無法收到任何touch事件:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
然後,在控制元件的內部分發事件的時候請求需要的事件(實際上就是禁止父控制元件攔截事件):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
//通知父容器不要攔截事件
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if ( <condition> ){
//通知父容器攔截此事件
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
parent.requestDisallowInterceptTouchEvent(false);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
這樣,就可以解決touch事件的衝突問題,從控制元件本身解決。內部攔截法使用起來稍顯複雜,需要修改兩個控制元件,一般情況下我們都通過外部攔截法解決滑動衝突,如果有特殊情況需要使用內部攔截法才會使用內部攔截法。
寫在最後
關於這兩種解決方案的例項以後會補上,再具體說明。