天天看點

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 ) ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

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,

);

}

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 類型)

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

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,

);

}

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 :

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

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"

,

0xFFFF8080

,

0xFF8080FF

);

colorAnim.setDuration(

3000

);

colorAnim.setEvaluator(

new

ArgbEvaluator());

colorAnim.setRepeatCount(ValueAnimator.INFINITE);

colorAnim.setRepeatMode(ValueAnimator.REVERSE);

colorAnim.start();

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(myView,

"scaleX"

,

.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 :

?

1 2 3 4 5

<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.touchtv></myview.touchlayout>

情景分析

Android ViewGroup觸摸事件攔截詳解

以下的情景的觸摸事件都是在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進行處理;

輸出 : ?

1 2 3 4 5 6 7 8 9 10 11 12 13

// 事件攔截

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,直接交給TouchLayout的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進行處理;

輸出 : ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// 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處理。

輸出 : ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// 事件攔截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處理。

輸出 : 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// 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().

輸出 : 

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// 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. )

輸出 : ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// 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的事件分發過程來驗證吧。代碼如下 : ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126

@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) !=

// 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 >=

; 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) !=

) { 

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) !=

) { 

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中進行處理。