Android ViewGroup攔截觸摸事件具體解釋


前言

在自己定義ViewGroup中。有時候須要實現觸摸事件攔截。比方ListView下拉刷新就是典型的觸摸事件攔截的樣例。

觸摸事件攔截就是在觸摸事件被parent view攔截,而不會分發給其child。即使觸摸發生在該child身上。被攔截的事件會轉到parent view的onTouchEvent方法中進行處理。

可是這個交互過程還是挺復雜的,有多種情況,今天我們就來分析一下吧。這篇分析文章已經放了一段時間了,假設有不論什么問題請高人指出。


觸摸事件的分發

簡單來說觸摸事件的分發會經過這么幾個順序,dispatchTouchEvent --> onInterceptTouchEvent --> onTouchEvent。事件攔截就在onInterceptTouchEvent方法中進行,在該方法中返回true即代表攔截觸摸事件。觸摸事件的分發是一個典型的隧道事件,即從上到下的過程。從視圖樹角度上來說,就是觸摸事件會從父視圖挨個傳遞到子視圖。

比方一個LinearLayout中又一個TextView。當觸摸這個TextView時觸摸事件會先打到LinearLayout。然后再到達TextView。假設LinearLayout將觸摸事件攔截了。那么TextView就會收到一個CANCEL事件,其它觸摸就收不到了。可是觸摸事件的處理過程是一個冒泡事件。還是以上面的TextView為例,正常情況下,事件從上到下分發到TextView上,TextView則會對該事件進行處理,假設TextView處理了該事件,即TextView的dispatchTouchEvent返回了true, 那么該事件就被消費了。可是假設TextView的dispatchTouchEvent返回的是false, 則代表這個事件沒有被處理,此時該事件就會從下到上(即從child 到 view group的過程)找parent view進行處理。

假設parent view也沒有處理。那么終於會交給Activity (假設是Activity窗體) 的onTouchEvent來處理。以下就是ViewGroup的事件分發過程。更具體的資料請參考Android Touch事件分發過程


觸摸事件的攔截

ViewGroup對於事件的攔截是一個復雜的流程,假設你想對觸摸事件進行攔截,那么你須要覆寫onInterceptTouchEvent方法,而且返回true。然后興許的事件就會被轉移到該ViewGroup的onTouchEvent方法進行處理。而在興許的事件處理過程中onInterceptTouchEvent中也不會收到興許事件,因此你也須要覆寫onTouchEvent方法。

我們首先看看onInterceptTouchEvent方法的官方說明 : 

public boolean onInterceptTouchEvent (MotionEvent ev)

Implement this method to intercept all touch screen motion events. This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.

Using this function takes some care, as it has a fairly complicated interaction with View.onTouchEvent(MotionEvent), and using it requires implementing that method as well as this one in the correct way. Events will be received in the following order:

You will receive the down event here.
    The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.

For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().

If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.

翻譯例如以下 : 

實現這種方法來攔截全部觸摸事件。這會使得您能夠監控到全部分發到你的子視圖的事件。然后您能夠隨時控制當前的手勢。
使用這種方法您須要花些精力,由於它與View.onTouchEvent(MotionEvent)的交互很復雜,而且要想使用這個功能還須要把當前ViewGroup的onTouchEvent方法和子控件的onTouchEvent方法正確地結合在一起使用。

事件獲取順序例如以下:


你將從這里開始接收ACTION_DOWN觸摸事件。
ACTION_DOWN觸摸事件能夠由該ViewGroup自己處理,也能夠由它的子控件的onTouchEvent進行處理。這就意味着你須要實現onTouchEvent(MotionEvent)方法而且返回true,這樣你才干夠接收到興許的事件(以免會繼續尋找父控件進行處理)。

假設你在onTouchEvent(MotionEvent)返回了true,那么在onInterceptTouchEvent()方法中您將不會再收到興許的事件,全部這些興許的事件(比如您在ACTION_DOWN中返回了true,那么ACTION_MOVE, ACTION_UP這些成為興許事件)將會被本類的onTouchEvent(MotionEvent)方法中被處理。



************
僅僅要您在onInterceptTouchEvent方法中返回false,每一個興許的事件(從當前事件到最后ACTION_UP事件)將會先分發到onInterceptTouchEvent中。然后再交給目標子控件的onTouchEvent處理 (前提是子控件的onTouchEvent返回是true )。



假設在onInterceptTouchEvent返回true,onInterceptTouchEvent方法中將不會收到興許的不論什么事件,目標子控件中除了ACTION_CANCEL外也不會接收全部這些興許事件。全部的興許事件將會被交付到你自己的onTouchEvent()方法中
************

觸摸事件攔截演示樣例

TouchLayout類 ( ViewGroup )
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.Scroller;

public class TouchLayout extends FrameLayout {

    private String TAG = TouchLayout.class.getSimpleName();

    public TouchLayout(Context context) {
        super(context);

    }

    public TouchLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
//        setClickable(true);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = super.dispatchTouchEvent(ev) ;
 
        return result;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 
        final int action = MotionEventCompat.getActionMasked(ev);
        // Always handle the case of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Do not intercept touch event, let the child handle it
            return false;
        }

        TouchUtils.showEventInfo(TAG + "#   onInterceptTouchEvent", action);

    
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        TouchUtils.showEventInfo(TAG + "# *** onTouchEvent", ev.getAction());
        Log.d(TAG, "### is Clickable = " + isClickable());
         return super.onTouchEvent(ev);
//        return true;
    }
    
}

TouchTv ( View 類型)

public class TouchTv extends TextView {

    private String TAG = TouchTv.class.getSimpleName();

    public TouchTv(Context context) {
        this(context, null);
    }

    public TouchTv(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchTv(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
//        setClickable(true);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        TouchUtils.showEventInfo(TAG + "#dispatchTouchEvent", ev.getAction());
        boolean result = super.dispatchTouchEvent(ev);
        Log.d(TAG, "### dispatchTouchEvent result = " + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        TouchUtils.showEventInfo(TAG + "#onTouchEvent", ev.getAction());
        boolean result = super.onTouchEvent(ev);
        Log.d(TAG, "### onTouchEvent result = " + result);
        return result;
    }
}
Activity : 

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.touch_event_intercept);

        View myView = findViewById(R.id.my_button);

        ValueAnimator colorAnim = ObjectAnimator.ofInt(myView,
                "backgroundColor", /* Red */
                0xFFFF8080, /* Blue */0xFF8080FF);
        colorAnim.setDuration(3000);
        colorAnim.setEvaluator(new ArgbEvaluator());
        colorAnim.setRepeatCount(ValueAnimator.INFINITE);
        colorAnim.setRepeatMode(ValueAnimator.REVERSE);
        colorAnim.start();

        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(myView, "scaleX",
                0.5f);
        objectAnimator.setDuration(3000);
        objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        objectAnimator.start();

        Log.d("", "### Activiti中getWindow()獲取的類型是 : " + this.getWindow());

        // state list
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[] {
                android.R.attr.state_enabled
        }, getResources().getDrawable(R.drawable.ic_launcher));
        stateListDrawable.addState(new int[] {
                android.R.attr.state_pressed
        }, getResources().getDrawable(R.drawable.ic_launcher));

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // Log.d("", "### activity dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        TouchUtils.showEventInfo("activity onTouchEvent", event.getAction());
        return super.onTouchEvent(event);
    }

}

touch_event_intercept.xml : 

<myview.TouchLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    tools:context="com.example.touch_event.MainActivity"
    tools:ignore="MergeRootFrame" >

    <myview.TouchTv
        android:id="@+id/my_button"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:background="#00aa00"
        android:gravity="center"
        android:text="@string/hello_world"
        android:textSize="30sp" />

</myview.TouchLayout>


情景分析



以下的情景的觸摸事件都是在TouchTv的范圍內。

情景1 

條件 :  
在TouchLayout的onInterceptTouchEvent中返回true進行事件攔截, 在TouchLayout的onTouchEvent中返回 true消費事件;

說明 : 
觸摸事件被攔截 。興許的事件不會進入到onInterceptTouchEvent ( If you return true from here, you will not receive any following events ),而直接進入TouchLayout的onTouchEvent方法進行處理。onTouchEvent返回true,表明事件被消費了。不會再冒泡給上面的Parent進行處理

輸出  : 
// 事件攔截
10-01 20:22:52.892: D/TouchLayout#   onInterceptTouchEvent(407): ### action -->  ACTION_DOWN
// 處理
10-01 20:22:52.892: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_DOWN
// DOWN的興許事件不經過onInterceptTouchEvent。直接交給的onTouchEvent處理
10-01 20:22:52.917: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:52.937: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:52.957: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:52.977: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:52.977: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:52.997: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:53.017: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_MOVE
10-01 20:22:53.017: D/TouchLayout# *** onTouchEvent(407): ### action -->  ACTION_UP

情景2

條件 :  
在TouchLayout的onInterceptTouchEvent中在ACTION_MOVE事件時返回true進行事件攔截, TouchTv的onTouchEvent中返回false,在TouchLayout的onTouchEvent中返回 true消費事件;

說明 : 
事件被攔截之前。會被分發給TouchTv的onTouchEvent進行處理;觸摸事件被攔截之后 。興許的事件不會進入到onInterceptTouchEvent,也不會交給TouchTv的onTouchEvent進行處理,而是直接進入TouchLayout的onTouchEvent方法進行處理。onTouchEvent返回true,表明事件被消費了,不會再冒泡給上面的Parent進行處理

輸出  : 
// DOWN中沒有對事件進行攔截,因此能夠被TouchTv進行處理
10-01 20:32:05.017: D/TouchLayout#   onInterceptTouchEvent(573): ### action -->  ACTION_DOWN
// TouchTv事件分發
10-01 20:32:05.017: D/TouchTv#dispatchTouchEvent(573): ### action -->  ACTION_DOWN
// TouchTv對事件進行處理,TouchTv的onTouchEvent返回false,導致事件交給TouchLayout的onTouchEvent處理
10-01 20:32:05.017: D/TouchTv#onTouchEvent(573): ### action -->  ACTION_DOWN
// TouchLayout的onTouchEvent處理DOWN事件
10-01 20:32:05.017: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_DOWN
// TouchLayout的onTouchEvent處理興許事件
10-01 20:32:05.062: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.082: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.267: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.287: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.307: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.307: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_MOVE
10-01 20:32:05.312: D/TouchLayout# *** onTouchEvent(573): ### action -->  ACTION_UP

情景3

條件 :  
在TouchLayout的onInterceptTouchEvent中返回true進行事件攔截, 但在TouchLayout的onTouchEvent中返回FALSE, 導致事件沒有被消費;

說明 : 
觸摸事件被攔截 ,興許的事件不會進入到onInterceptTouchEvent,而直接進入TouchLayout的onTouchEvent方法進行處理。

onTouchEvent返回false表明事件沒有被消費,須要交給parent處理,假設終於該事件沒有被處理。那么事件交給Activity的onTouchEvent處理。


輸出  : 
// 事件攔截onInterceptTouchEvent
10-01 20:16:03.617: D/TouchLayout#   onInterceptTouchEvent(32675): ### action -->  ACTION_DOWN
// 事件處理onTouchEvent
10-01 20:16:03.617: D/TouchLayout# *** onTouchEvent(32675): ### action -->  ACTION_DOWN
// TouchLayout的dispatchTouchEvent終於返回了false,
10-01 20:16:03.617: D/TouchLayout(32675): ### dispatchTouchEvent, return false
// 事件沒有被處理。終於交給了Activity的onTouchEvent處理
10-01 20:16:03.617: D/activity onTouchEvent(32675): ### action -->  ACTION_DOWN
10-01 20:16:03.697: D/activity onTouchEvent(32675): ### action -->  ACTION_MOVE
10-01 20:16:03.712: D/activity onTouchEvent(32675): ### action -->  ACTION_MOVE
10-01 20:16:03.732: D/activity onTouchEvent(32675): ### action -->  ACTION_MOVE
10-01 20:16:03.882: D/activity onTouchEvent(32675): ### action -->  ACTION_MOVE
10-01 20:16:03.897: D/activity onTouchEvent(32675): ### action -->  ACTION_MOVE
10-01 20:16:03.917: D/activity onTouchEvent(32675): ### action -->  ACTION_UP

情景4

條件 :  
在TouchLayout的onInterceptTouchEvent中返回FALSE,不正確觸摸進行事件攔截, TouchLayout的onTouchEvent中返回true,但在TouchTv的onTouchEvent中返回FALSE, 導致事件沒有被消費;

說明 : 
觸摸事件不進行攔截,因此事件終於進入到TouchTv的 onTouchEvent,但其返回false 表明事件沒有被消費,須要交給parent處理。假設終於該事件沒有被處理。那么事件交給Activity的onTouchEvent處理。

輸出  : 

// TouchLayout不正確事件進行攔截
10-01 20:43:04.682: D/TouchLayout#   onInterceptTouchEvent(814): ### action -->  ACTION_DOWN
// 事件被TouchTv分發
10-01 20:43:04.682: D/TouchTv#dispatchTouchEvent(814): ### action -->  ACTION_DOWN
// 事件被TouchTv處理
10-01 20:43:04.682: D/TouchTv#onTouchEvent(814): ### action -->  ACTION_DOWN
// 事件被TouchTv的處理結果為false,因此該事件須要找parent來處理
10-01 20:43:04.682: D/TouchTv(814): ### dispatchTouchEvent result = false
// 事件被交給TouchTv的parent的onTouchEvent處理,即TouchLayout的onTouchEvent,該方法返回true
// 因此興許事件繼續交給TouchLayout處理
10-01 20:43:04.682: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_DOWN
10-01 20:43:04.727: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_MOVE
10-01 20:43:04.747: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_MOVE
10-01 20:43:04.872: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_MOVE
10-01 20:43:04.892: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_MOVE
10-01 20:43:04.892: D/TouchLayout# *** onTouchEvent(814): ### action -->  ACTION_UP


情景5

條件 :  
在TouchLayout的onInterceptTouchEvent中返回FALSE,不正確觸摸進行事件攔截, 但在TouchTv的onTouchEvent中返回true。 導致事件被消費;

說明 : 
觸摸事件不進行攔截,因此事件終於進入到TouchTv的 onTouchEvent,但其返回true 表明事件被消費了,那么興許事件將會先到TouchLayout的onInterceptTouchEvent,然后再分發給TouchTv。原文描寫敘述為 : For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().

輸出  : 

// TouchLayout不攔截事件,因此事件分發給TouchTv進行處理。而TouchTv的處理結果為true,因此興許的事件將會先從
// TouchLayout的onInterceptTouchEvent再到TouchTv的onTouchEvent
10-01 20:48:49.612: D/TouchLayout#   onInterceptTouchEvent(1030): ### action -->  ACTION_DOWN
// TouchTv處理事件
10-01 20:48:49.612: D/TouchTv#dispatchTouchEvent(1030): ### action -->  ACTION_DOWN
10-01 20:48:49.612: D/TouchTv#onTouchEvent(1030): ### action -->  ACTION_DOWN
10-01 20:48:49.612: D/TouchTv(1030): ### dispatchTouchEvent result = true
// 興許事件從TouchLayout的onInterceptTouchEvent再到TouchTv的onTouchEvent
10-01 20:48:49.697: D/TouchLayout#   onInterceptTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.697: D/TouchTv#dispatchTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.697: D/TouchTv#onTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.697: D/TouchTv(1030): ### dispatchTouchEvent result = true
10-01 20:48:49.717: D/TouchLayout#   onInterceptTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.717: D/TouchTv#dispatchTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.717: D/TouchTv#onTouchEvent(1030): ### action -->  ACTION_MOVE
10-01 20:48:49.717: D/TouchTv(1030): ### dispatchTouchEvent result = true
// UP事件直接在TouchTv中進行分發
10-01 20:48:49.782: D/TouchTv#dispatchTouchEvent(1030): ### action -->  ACTION_UP
10-01 20:48:49.782: D/TouchTv#onTouchEvent(1030): ### action -->  ACTION_UP
10-01 20:48:49.782: D/TouchTv(1030): ### dispatchTouchEvent result = true

情景6

條件 :  
在TouchLayout的onInterceptTouchEvent中的DOWN事件中返回FALSE,但在MOVE事件中返回true, 且TouchTv的onTouchEvent返回true。

說明 : 
觸摸事件對DOWN事件不進行攔截,因此TouchTv能夠正常的處理。可是在MOVE時對事件進行了攔截,那么TouchTv就無法接收到MOVE以及后面的事件了,它會收到一個CANCEL事件,興許的事件將會被TouchLayout的onTouchEvent進行處理。( If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here. )

輸出  : 
// TouchLayout不正確DOWN進行攔截
10-01 20:56:37.642: D/TouchLayout#   onInterceptTouchEvent(1205): ### action -->  ACTION_DOWN
// TouchTv分發與處理DOWN事件
10-01 20:56:37.642: D/TouchTv#dispatchTouchEvent(1205): ### action -->  ACTION_DOWN
10-01 20:56:37.642: D/TouchTv#onTouchEvent(1205): ### action -->  ACTION_DOWN
10-01 20:56:37.642: D/TouchTv(1205): ### dispatchTouchEvent result = true
// TouchLayout對MOVE事件進行攔截
10-01 20:56:37.712: D/TouchLayout#   onInterceptTouchEvent(1205): ### action -->  ACTION_MOVE
// TouchTv收到一個CANCEL事件,然后不會不到MOVE以及興許的事件
10-01 20:56:37.712: D/TouchTv#dispatchTouchEvent(1205): ### action -->  ACTION_CANCEL
10-01 20:56:37.712: D/TouchTv#onTouchEvent(1205): ### action -->  ACTION_CANCEL
10-01 20:56:37.712: D/TouchTv(1205): ### dispatchTouchEvent result = true
// MOVE以及興許事件被TouchLayout處理
10-01 20:56:37.727: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:37.747: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:37.762: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:37.777: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:37.797: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:37.997: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:38.012: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_MOVE
10-01 20:56:38.017: D/TouchLayout# *** onTouchEvent(1205): ### action -->  ACTION_UP

總結

以上的幾種情況就是我們常常遇到的了。總結起來有幾個重要的點 :

1、假設Parent ViewGroup的onInterceptTouchEvent返回false, 而且觸摸的目標view對於觸摸事件的處理結果返回的是true,那么興許事件會先經過parent 的onInterceptTouchEvent, 然后再交給目標view進行處理;

2、假設Parent ViewGroup的onInterceptTouchEvent返回true,即對事件進行攔截。那么事件將不會再經過onInterceptTouchEvent。而是直接進入到onTouchEvent進行處理;假設onTouchEvent返回true,則表示該事件被處理了;假設返回FALSE,則代表事件沒有被處理,那么事件會被上交給它的parent來處理,假設沒有parent來處理,那么終於會交給Activity來處理;

3、假設用戶在觸摸的某個事件才攔截。那么目標view會收到一個CANCEL事件。然后興許的事件不會再交給目標view,而被轉交給Parent的onTouchEvent方法進行處理。

比方情景6其中。在TouchLayout的DOWN時不正確事件進行攔截,這時事件會被TouchTv正常處理。

可是在MOVE時事件被攔截了,此時TouchTv收到了一個CANCEL事件。MOVE以及興許的事件就交給了TouchLayout進行處理。這個情景就是我們做下拉刷新時要用的場景了。

我們結合ViewGroup的事件分發過程來驗證吧。代碼例如以下 : 
/** 
 * {@inheritDoc} 
 */  
@Override  
public boolean dispatchTouchEvent(MotionEvent ev) {  
    if (!onFilterTouchEventForSecurity(ev)) {  
        return false;  
    }  
  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    // 是否禁用攔截,假設為true表示不能攔截事件;反之。則為能夠攔截事件  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    // ACTION_DOWN事件,即按下事件  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
        // If we're disallowing intercept or if we're allowing and we didn't  
        // intercept。假設不同意事件攔截或者不攔截該事件,那么運行以下的操作  
        if (disallowIntercept || !onInterceptTouchEvent(ev))         // 1、是否禁用攔截、是否攔截事件的推斷  
            // reset this event's action (just to protect ourselves)  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            // We know we want to dispatch the event down, find a child  
            // who can handle it, start with the front-most child.  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  
  
            for (int i = count - 1; i >= 0; i--)        // 2、迭代全部子view,查找觸摸事件在哪個子view的坐標范圍內  
                final View child = children[i];  
                // 該child是可見的  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    // 3、獲取child的坐標范圍  
                    child.getHitRect(frame);                 
                    // 4、推斷發生該事件坐標是否在該child坐標范圍內  
                    if (frame.contains(scrolledXInt, scrolledYInt))      
                        // offset the event to the view's coordinate system  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        // 5、child處理該事件,假設返回true,那么mMotionTarget為該child。正常情況下,  
                        // dispatchTouchEvent(ev)的返回值即onTouchEcent的返回值。因此onTouchEcent假設返回為true,  
                        // 那么mMotionTarget為觸摸事件所在位置的child。  
                        if (child.dispatchTouchEvent(ev))       
                            // 6、 mMotionTarget為該child
                            mMotionTarget = child;  
                            return true;  
                        }  
               
                    }  
                }  
            }  
        }  
    }// end if  
  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
  
    if (isUpOrCancel) {  
        // Note, we've already copied the previous state to our local  
        // variable, so this takes effect on the next event  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
  
  	// 觸摸事件的目標view, 即觸摸所在的view
    final View target = mMotionTarget;  
    // 7、假設mMotionTarget為空。那么運行super.super.dispatchTouchEvent(ev),  
    // 即View.dispatchTouchEvent(ev),就是該View Group自己處理該touch事件,僅僅是又走了一遍View的分發過程而已.  
    // 攔截事件或者在不攔截事件且target view的onTouchEvent返回false的情況都會運行到這一步.  
    if (target == null) {  
        // We don't have a target, this means we're handling the  
        // event as a regular view.  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
 
    // 8、假設沒有禁用事件攔截,而且onInterceptTouchEvent(ev)返回為true。即進行事件攔截.  ( 在某個事件時攔截的情形 )  
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
        //   
        if (!target.dispatchTouchEvent(ev)) {  
            // target didn't handle ACTION_CANCEL. not much we can do  
            // but they should have.  
        }  
        // clear the target  
        mMotionTarget = null;  
        // Don't dispatch this event to our own view, because we already  
        // saw it when intercepting; we just want to give the following  
        // event to the normal onTouchEvent().  
        return true;  
    }  
  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
  
    // finally offset the event to the target's coordinate system and  
    // dispatch the event.  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }  
    // 9、事件不攔截,且target view在ACTION_DOWN時返回true。那么興許事件由target來處理事件  
    return target.dispatchTouchEvent(ev);  
}  
假設不正確事件進來攔截,且TouchTv對事件的處理返回true。那么在DOWN事件時,mMotionTarget就是TouchTv,興許的事件就會通過凝視9來處理,即直接交給TouvhTv來處理。假設在DOWN時就攔截事件。那么mMotionTarget為空,則會運行凝視7出的代碼,一直調用super.dispatchTouchEvent處理事件。即調用本類的事件處理,終於會調用onTouchEvent方法。假設在DOWN時不攔截。MOVE時攔截,那么會引發凝視8的代碼,target view收到一個cancel事件。且mMotionTarget被置空,興許事件在凝視7出的代理進行處理,即在自己的onTouchEvent中進行處理。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM