Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android View的事件分發機制探索

Android View的事件分發機制探索

編輯:關於Android編程

概述

Android事件傳遞機制也是Android系統中比較重要的一塊,事件類型有很多種,這裡主要討論TouchEvent的事件在framework層的傳遞處理機制。因為對於App開發人員來說,理解framework層的事件傳遞機制,就差不多了。

帶著問題來思考整個事件分發過程。

1、為什麼要有事件分發過程?

當Android設備的屏幕,接收到觸摸的動作時,屏幕驅動把壓力信號(包括壓力大小,壓力位置等)傳遞給系統底層,然後操作系統經過一系列的處理,然後把觸摸事件一層一層的向上傳遞,最終事件會被准確的傳遞到產生事件的對象上,系統會遍歷每一個View對象,然後計算觸摸點在哪一個View中。比如A和B兩個View,是兄弟View,AView產生的觸摸事件,是不會被分發到B上面的。

2、怎麼看待事件序列?

在Android系統中,一個單獨的事件基本上是沒什麼作用的,只有一個事件序列,才有意義。一個事件序列正常情況下,定義為 DOWN、MOVE(0或者多個)、UP/CANCEL。事件序列以DOWN事件開始,中間會有0或者多個MOVE事件,最後以UP事件或者CANCEL事件結束。

DOWN事件作為序列的開始,有一個很重要的職責,就是尋找事件序列的接受者,怎麼理解呢?framework 在DOWN事件的傳遞過程中,需要根據View事件處理方法(onTouchEvent)的返回值來確定事件序列的接受者。如果一個View的onTouchEvent事件,在處理DOWN事件的時候返回true,說明它願意接受並處理該事件序列。

3、Android的framework層如何處理事件的分發過程?

觸摸事件到了framework層之後,首先會被傳遞到Activity,然後Activity會把事件委托給它內部的Window對象進行分發處理,而Window對象又會委托它內部的DecorView進行事件分發處理。我們都知道,DecorView是整棵View樹的根節點,所以整個事件傳遞過程的復雜度就是事件在View樹種分發傳遞的復雜度。 Android View框架提供了3個對事件的主要操作概念。

1、事件的分發機制,dispatchTouchEvent。主要是parent根據觸摸事件的產生位置,以及child是否願意負責處理該系列事件等狀態,向其child分發事件的機制。

2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。

3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。

4、上述三個機制,是怎麼向其調用者傳遞處理結果的?

在Java中,傳遞計算結果,有很多種途徑,這裡采用的是一種適用於同步調用的方法,返回值的方法。每個機制都使用boolean類型作為其返回值,那麼每個機制的每個返回值是什麼含義呢。

1、事件的分發機制,dispatchTouchEvent。

true-事件被以該節點為根節點的View樹成功處理,此時該事件就算是處理完成了,事件不會再向上返還給View的父節點(把事件分發過來的那個節點)。

false-以該節點為根節點的View樹種,沒有一個View(包括該View)成功處理了此事件,所以事件會向上返還給View的父節點(把事件分發過來的那個節點)。

2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。

true-當前ViewGroup(因為View中沒有該方法,而沒有child的VIew也不需要有攔截機制)希望該事件不再傳遞給其child,而是希望自己處理。

false-當前ViewGroup不准備攔截該事件,事件正常向下分發給其child。

3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。

true-表示該View成功處理了該事件,該處理結果會向上通知給其parent。

false-表示該View沒有成功處理該事件,那麼它的parent會有機會來處理該事件(parent標記為事件序列接受者,parent 的 onTouchEvent 在 Down 事件時返回true)。

源代碼分析

源代碼基於SDK 23

View:

1、dispatchTouchEvent:

/**把事件分發到目標對象,因為這裡是View對象,默認不含有child,所以這裡他會把事件分發給自己 */

public boolean dispatchTouchEvent(MotionEvent event);

源代碼:

不給出,有興趣的讀者執行查閱SDK

偽代碼:

public boolean dispatchTouchEvent(MotionEvent event){
    boolean result = false;
    //如果有事件監聽器,先讓監聽器處理事件。
    if (mOnTouchListener.onTouch(event)) {
        //如果監聽器成功處理了該事件,處理結果設置為true。
        result = true;
    }
    //如果沒有監聽器,就調用自身的onTouchEvent方法來處理事件。
    if (!resutlt && onTouchEvent(event)) {
        //如果自身的onTouchEvent成功處理事件,處理結果設置為true。
        result = true;
    }
    return result;
}


ViewGroup:

1、onInterceptTouchEvent

/**默認實現是返回false,也就是默認不攔截任何事件 */

public boolean onInterceptTouchEvent(MotionEvent ev);

2、dispatchTouchEvent

/**根據內部攔截狀態,向其child或者自己分發事件 */

public boolean dispatchTouchEvent(MotionEvent ev);

源代碼:

不給出,有興趣的讀者執行查閱SDK

偽代碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ACTION_DOWN事件 || 沒有事件處理對象) {
        if (允許攔截事件,該標志位由child調用requestDisallowInterceptTouchEvent設置) {
			//查詢攔截機制的結果,根據該結果來判斷是否需要攔截
            intercepted = onInterceptTouchEvent(ev);
        } else {
			//不允許攔截,那麼不攔截
            intercepted = false;
        }
    } else { 
	    //不是DOWN,並且有處理對象,允許攔截,中斷事件傳遞
        intercepted = true;
    }

    if (不取消 && 不攔截) {
        if (ACTION_DOWN) { //找尋接收事件序列的對象
            for (遍歷所有childView) {
                if (觸摸點不在childView內部) {
                    continue;
                }
                if (childView.dispatchTouchEvent(event)) {
                    保存處理該事件的View,後續事件直接傳遞到該View,不要重新計算;
                }
            }
        }

        if (還沒有事件處理對象) {
			//當前View樹中沒找到合適的child處理對象,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己
            super.dispatchTouchEvent(event);
        } else {
			//傳遞給child
            childView.dispatchTouchEvent(event);
        }
    } else if (攔截) {
		//攔截事件,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己
        super.dispatchTouchEvent(event);
	}

    return 處理結果;
}


3、requestDisallowInterceptTouchEvent

/**干澀parent的事件分發機制,通知parent,是否攔截後續事件,如果設置為true,parent就不會攔截該事件,不管什麼狀態。設置為false,parent走正常的攔截流程 */

publicvoidrequestDisallowInterceptTouchEvent(booleandisallowIntercept);

源代碼:

不給出,有興趣的讀者執行查閱SDK

偽代碼:

 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (已經是當前要設置的狀態) {
        // 已經處於這個狀態, 假設我們的parent也是這個狀態
        return;
    }
    設置該狀態;
    // 傳遞給parent
    if (有父容器) {
        設置父容器的攔截狀態;
    }
}


自己動手

我們都知道,如果ScrollView內部嵌套ListView,那麼ListView是不可以滑動的,效果如下圖所示:

\

那麼其實這就是典型的事件沖突問題,就是說,原本應該被ListView用來上下滑動的事件,被ScrollView攔截了。就導致ListView不能正常滑動。

我們來看一下ScrollView的源代碼:

onInterceptTouchEvent的偽代碼:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    /*
     * 這個方法決定了我們是否要攔截事件.
     * 如果返回true, onTouchEvent會被調用並且我們開始做實際的Scroll操作.
     */

    /*
    * 大部分循環的狀態: 用戶在再拖拽的狀態並且正在移動手指,
    * 我們希望攔截這個事件
    */
    final int action = ev.getAction();
    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
        return true;
    }
	
	//其他操作
	......................
}


所以正常的上下拖拽,ScrollView都會攔截。

那麼我們下面改進一下,就是當我們滑動ScrollView中非ListView的區域時,ScrollView滑動,而我們滑動ListView的時候,ListView滑動,效果看起來如下圖所示:

\

這裡解決方法如下:

既然ScrollView會攔截事件,那麼當我們滑動ListView的時候,我們不希望ScrollView攔截事件,這裡我們繼承ListView,在onTouchEvent中,請求ScrollView不要攔截事件。

部分代碼如下:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    super.onTouchEvent(ev);
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            getParent().requestDisallowInterceptTouchEvent(false);
            break;
        default:
            break;
    }
    return  true;
}


這樣就可以很好的解決事件沖突的問題。

還有一種方法就是覆寫parent的onInterceptTouchEvent方法,來修改事件攔截的狀態。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved