1. 程式人生 > >關於view事件體系的幾個問題

關於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事件的衝突問題,從控制元件本身解決。內部攔截法使用起來稍顯複雜,需要修改兩個控制元件,一般情況下我們都通過外部攔截法解決滑動衝突,如果有特殊情況需要使用內部攔截法才會使用內部攔截法。

寫在最後

關於這兩種解決方案的例項以後會補上,再具體說明。