您的位置:首页 > 移动开发 > Android开发

细说Android事件传递机制(dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent)

2015-12-25 11:06 796 查看

一、View的dispatchTouchEvent和onTouchEvent

探讨Android事件传递机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。而事件即MotionEvent,最重要的有3个:(1)MotionEvent.ACTION_DOWN
按下View,是所有事件的开始(2)MotionEvent.ACTION_MOVE 滑动事件

(3)MotionEvent.ACTION_UP 与down对应,表示抬起

另外,明确事件传递机制的最终目的都是为了触发执行View的点击监听和触摸监听: ******.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

Log.i(tag, "testLinelayout---onClick...");

}});

*******.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

// TODO Auto-generated method stub

return false;

}});

我们简称为onClick监听和onTouch监听,一般程序会注册这两个监听。从上面可以看到,onTouch监听里默认return false。不要小看了这个return false,后面可以看到它有大用。 对于View来说,事件传递机制有两个函数:dispatchTouchEvent负责分发事件,在dispatch***里又会调用onTouchEvent表示执行事件,或者说消费事件,结合源码分析其流程。事件传递的入口是View的dispatchTouchEvent()函数:

view sourceprint?

01.
/**

02.
* Pass the touch screen motion event down to the target view, or this

03.
* view if it is the target.

04.
*

05.
* @param event The motion event to be dispatched.

06.
* @return True if the event was handled by the view, false otherwise.

07.
*/

08.
public

boolean
dispatchTouchEvent(MotionEvent event) {

09.
if

(mInputEventConsistencyVerifier !=
null
) {

10.
mInputEventConsistencyVerifier.onTouchEvent(event,
0
);

11.
}

12.
 
13.
if

(onFilterTouchEventForSecurity(event)) {

14.
//noinspection SimplifiableIfStatement

15.
ListenerInfo li = mListenerInfo;

16.
if

(li !=
null
&& li.mOnTouchListener !=
null
&& (mViewFlags & ENABLED_MASK) == ENABLED

17.
&& li.mOnTouchListener.onTouch(
this
, event)) {

18.
return

true
;

19.
}

20.
 
21.
if

(onTouchEvent(event)) {

22.
return

true
;

23.
}

24.
}

25.
 
26.
if

(mInputEventConsistencyVerifier !=
null
) {

27.
mInputEventConsistencyVerifier.onUnhandledEvent(event,
0
);

28.
}

29.
return

false
;

30.
}


找到这个判断: if (li !=null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED

&& li.mOnTouchListener.onTouch(this, event)) {

return true;

}他会执行View的OnTouchListener.onTouch这个函数,也就是上面说的onTouch监听。里面有三个判断,如果三个都为1,就会执行return true,不往下走了。而默认的onTouch监听返回false,只要一个是false,就不会返回true。接着往下看,程序执行onTouchEvent:

view sourceprint?

1.
if

(onTouchEvent(event)) {

2.
return

true
;

3.
}


onTouchEvent的
20000
源码比较多,贴最重要的:

view sourceprint?

01.
if

(!mHasPerformedLongPress) {

02.
// This is a tap, so remove the longpress check

03.
removeLongPressCallback();

04.
 
05.
// Only perform take click actions if we were in the pressed state

06.
if

(!focusTaken) {

07.
// Use a Runnable and post this rather than calling

08.
// performClick directly. This lets other visual state

09.
// of the view update before click actions start.

10.
if

(mPerformClick ==
null
) {

11.
mPerformClick =
new
PerformClick();

12.
}

13.
if

(!post(mPerformClick)) {

14.
performClick();

15.
}

16.
}

17.
}


可以看到有个performClick(),它的源码里有这么一句 li.mOnClickListener.onClick(this);

view sourceprint?

01.
public

boolean
performClick() {

02.
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

03.
 
04.
ListenerInfo li = mListenerInfo;

05.
if

(li !=
null
&& li.mOnClickListener !=
null
) {

06.
playSoundEffect(SoundEffectConstants.CLICK);

07.
<strong>li.mOnClickListener.onClick(
this
);</strong>

08.
return

true
;

09.
}

10.
 
11.
return

false
;

12.
}


终于对上了,它执行了我们注册的onClick监听。当然执行前会经过一系列判断,是否注册了监听等。

总结:1、事件入口是dispatchTouchEvent(),它会先执行注册的onTouch监听,如果一切顺利的话,接着执行onTouchEvent,在onTouchEvent里会执行onClick监听。2、无论是dispatchTouchEvent还是onTouchEvent,如果返回true表示这个事件已经被消费、处理了,不再往下传了。在dispathTouchEvent的源码里可以看到,如果onTouchEvent返回了true,那么它也返回true。如果dispatch***在执行onTouch监听的时候,onTouch返回了true,那么它也返回true,这个事件提前被onTouch消费掉了。就不再执行onTouchEvent了,更别说onClick监听了。3、我们通常在onTouch监听了设置图片一旦被触摸就改变它的背景、透明度之类的,这个onTouch表示事件的时机。而在onClick监听了去具体干某些事。

下面通过代码来说明,自定义一个TestButton继承自Button,重写它的dispath***和onTouchEvent方法,为了简单只关注down和up事件。

view sourceprint?

01.
package

org.yanzi.ui;

02.
 
03.
import

android.content.Context;

04.
import

android.util.AttributeSet;

05.
import

android.util.Log;

06.
import

android.view.MotionEvent;

07.
import

android.widget.Button;

08.
 
09.
public

class
TestButton
extends

Button {

10.
private

final
static
String tag =
"yan"
;

11.
public

TestButton(Context context, AttributeSet attrs) {

12.
super
(context, attrs);

13.
// TODO Auto-generated constructor stub

14.
}

15.
@Override

16.
public

boolean
onTouchEvent(MotionEvent event) {

17.
// TODO Auto-generated method stub

18.
switch
(event.getAction()){

19.
case

MotionEvent.ACTION_DOWN:

20.
Log.i(tag,
"TestButton-onTouchEvent-ACTION_DOWN..."
);

21.
break
;

22.
case

MotionEvent.ACTION_UP:

23.
Log.i(tag,
"TestButton-onTouchEvent-ACTION_UP..."
);

24.
break
;

25.
default
:
break
;

26.
}

27.
return

super
.onTouchEvent(event);

28.
}

29.
 
30.
@Override

31.
public

boolean
dispatchTouchEvent(MotionEvent event) {

32.
// TODO Auto-generated method stub

33.
switch
(event.getAction()){

34.
case

MotionEvent.ACTION_DOWN:

35.
Log.i(tag,
"TestButton-dispatchTouchEvent-ACTION_DOWN..."
);

36.
break
;

37.
case

MotionEvent.ACTION_UP:

38.
Log.i(tag,
"TestButton-dispatchTouchEvent-ACTION_UP..."
);

39.
break
;

40.
default
:
break
;

41.
}

42.
 
43.
return

super
.dispatchTouchEvent(event);

44.
}

45.
 
46.
}


在Activity里注册两个监听:

view sourceprint?

01.
testBtn.setOnClickListener(
new

View.OnClickListener() {

02.
 
03.
@Override

04.
public

void
onClick(View v) {

05.
// TODO Auto-generated method stub

06.
Log.i(tag,
"testBtn---onClick..."
);

07.
}

08.
});

09.
testBtn.setOnTouchListener(
new

View.OnTouchListener() {

10.
 
11.
@Override

12.
public

boolean
onTouch(View v, MotionEvent event) {

13.
// TODO Auto-generated method stub

14.
switch
(event.getAction()){

15.
case

MotionEvent.ACTION_DOWN:

16.
Log.i(tag,
"testBtn-onTouch-ACTION_DOWN..."
);

17.
break
;

18.
case

MotionEvent.ACTION_UP:

19.
Log.i(tag,
"testBtn-onTouch-ACTION_UP..."
);

20.
break
;

21.
default
:
break
;

22.
 
23.
}

24.
return

false
;

25.
}

26.
});


同时复写Activity的dispatch方法和onTouchEvent方法:

view sourceprint?

01.
@Override

02.
public

boolean
dispatchTouchEvent(MotionEvent ev) {

03.
// TODO Auto-generated method stub

04.
switch
(ev.getAction()){

05.
case

MotionEvent.ACTION_DOWN:

06.
Log.i(tag,
"MainActivity-dispatchTouchEvent-ACTION_DOWN..."
);

07.
break
;

08.
case

MotionEvent.ACTION_UP:

09.
Log.i(tag,
"MainActivity-dispatchTouchEvent-ACTION_UP..."
);

10.
break
;

11.
default
:
break
;

12.
}

13.
return

super
.dispatchTouchEvent(ev);

14.
}

15.
 
16.
@Override

17.
public

boolean
onTouchEvent(MotionEvent event) {

18.
// TODO Auto-generated method stub

19.
switch
(event.getAction()){

20.
case

MotionEvent.ACTION_DOWN:

21.
Log.i(tag,
"MainActivity-onTouchEvent-ACTION_DOWN..."
);

22.
break
;

23.
case

MotionEvent.ACTION_UP:

24.
Log.i(tag,
"MainActivity-onTouchEvent-ACTION_UP..."
);

25.
break
;

26.
default
:
break
;

27.
}

28.
return

super
.onTouchEvent(event);

29.
}


最终一次点击,打印信息如下: Line 33: 01-08 14:59:45.847 I/yan ( 4613): MainActivity-dispatchTouchEvent-ACTION_DOWN...

Line 35: 01-08 14:59:45.849 I/yan ( 4613): TestButton-dispatchTouchEvent-ACTION_DOWN...

Line 37: 01-08 14:59:45.849 I/yan ( 4613): testBtn-onTouch-ACTION_DOWN...

Line 39: 01-08 14:59:45.849 I/yan ( 4613): TestButton-onTouchEvent-ACTION_DOWN...

Line 41: 01-08 14:59:45.939 I/yan ( 4613): MainActivity-dispatchTouchEvent-ACTION_UP...

Line 43: 01-08 14:59:45.941 I/yan ( 4613): TestButton-dispatchTouchEvent-ACTION_UP...

Line 45: 01-08 14:59:45.944 I/yan ( 4613): testBtn-onTouch-ACTION_UP...

Line 47: 01-08 14:59:45.946 I/yan ( 4613): TestButton-onTouchEvent-ACTION_UP...

Line 49: 01-08 14:59:45.974 I/yan ( 4613): testBtn---onClick...

事件先由Activity的dispatchTouchEvent进行分发,然后TestButton的dispatchTouchEvent进行分发,接着执行onTouch监听,然后执行onTouchEvent。第二次UP动作的时候,在onTouchEvent里又执行了onClick监听。 如果我们想这个TestButton只能执行onTouch监听不能执行onClick监听,方法有很多。在onTouch监听里默认返回false改为true,如下:

view sourceprint?

01.
testBtn.setOnTouchListener(
new

View.OnTouchListener() {

02.
 
03.
@Override

04.
public

boolean
onTouch(View v, MotionEvent event) {

05.
// TODO Auto-generated method stub

06.
switch
(event.getAction()){

07.
case

MotionEvent.ACTION_DOWN:

08.
Log.i(tag,
"testBtn-onTouch-ACTION_DOWN..."
);

09.
break
;

10.
case

MotionEvent.ACTION_UP:

11.
Log.i(tag,
"testBtn-onTouch-ACTION_UP..."
);

12.
break
;

13.
default
:
break
;

14.
 
15.
}

16.
return

true
;

17.
}

18.
});


事件流程为: Line 75: 01-08 15:05:51.627 I/yan ( 5262): MainActivity-dispatchTouchEvent-ACTION_DOWN...

Line 77: 01-08 15:05:51.628 I/yan ( 5262): TestButton-dispatchTouchEvent-ACTION_DOWN...

Line 79: 01-08 15:05:51.629 I/yan ( 5262): testBtn-onTouch-ACTION_DOWN...

Line 81: 01-08 15:05:51.689 I/yan ( 5262): MainActivity-dispatchTouchEvent-ACTION_UP...

Line 83: 01-08 15:05:51.691 I/yan ( 5262): TestButton-dispatchTouchEvent-ACTION_UP...

Line 85: 01-08 15:05:51.695 I/yan ( 5262): testBtn-onTouch-ACTION_UP...

可以看到压根就没执行onTouchEvent。因为onTouch返回了true,已提前将这个事件消费了,就不往下传了,dispatch流程提前终止。

二、ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

再来看ViewGroup,在复写ViewGroup时可以发现它的onTouchEvent在在View里的,表示这两个方法是一样的。但dispatchTouchEvent是在ViewGroup里的,表示和View的dispatchTouchEvent不一样,多了一个onInterceptTouchEvent函数,表示拦截的意思。链接 打个很形象的比喻,这玩意就像个秘书、谋士。为啥View没有呢,因为它级别不够,一个Button里面是不可能有子View的。但LinearLayout(继承ViewGroup)就有孩子(子布局),这个onInterceptTouchEvent就会判断事件要不要通知它的孩子呢。它的源码如下:

view sourceprint?

001.
public

boolean
dispatchTouchEvent(MotionEvent ev) {

002.
if

(mInputEventConsistencyVerifier !=
null
) {

003.
mInputEventConsistencyVerifier.onTouchEvent(ev,
1
);

004.
}

005.
 
006.
boolean

handled =
false
;

007.
if

(onFilterTouchEventForSecurity(ev)) {

008.
final

int
action = ev.getAction();

009.
final

int
actionMasked = action & MotionEvent.ACTION_MASK;

010.
 
011.
// Handle an initial down.

012.
if

(actionMasked == MotionEvent.ACTION_DOWN) {

013.
// Throw away all previous state when starting a new touch gesture.

014.
// The framework may have dropped the up or cancel event for the previous gesture

015.
// due to an app switch, ANR, or some other state change.

016.
cancelAndClearTouchTargets(ev);

017.
resetTouchState();

018.
}

019.
 
020.
// Check for interception.

021.
final

boolean
intercepted;

022.
if

(actionMasked == MotionEvent.ACTION_DOWN

023.
|| mFirstTouchTarget !=
null
) {

024.
final

boolean
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) !=
0
;

025.
if

(!disallowIntercept) {

026.
<strong>intercepted = onInterceptTouchEvent(ev);</strong>

027.
ev.setAction(action);
// restore action in case it was changed

028.
}
else
{

029.
intercepted =
false
;

030.
}

031.
}
else
{

032.
// There are no touch targets and this action is not an initial down

033.
// so this view group continues to intercept touches.

034.
intercepted =
true
;

035.
}

036.
 
037.
// Check for cancelation.

038.
final

boolean
canceled = resetCancelNextUpFlag(
this
)

039.
|| actionMasked == MotionEvent.ACTION_CANCEL;

040.
 
041.
// Update list of touch targets for pointer down, if needed.

042.
final

boolean
split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) !=
0
;

043.
TouchTarget newTouchTarget =
null
;

044.
boolean

alreadyDispatchedToNewTouchTarget =
false
;

045.
<strong>
if

(!canceled && !intercepted)</strong> {

046.
if

(actionMasked == MotionEvent.ACTION_DOWN

047.
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

048.
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

049.
final

int
actionIndex = ev.getActionIndex();
// always 0 for down

050.
final

int
idBitsToAssign = split ?

1
<< ev.getPointerId(actionIndex)

051.
: TouchTarget.ALL_POINTER_IDS;

052.
 
053.
// Clean up earlier touch targets for this pointer id in case they

054.
// have become out of sync.

055.
removePointersFromTouchTargets(idBitsToAssign);

056.
 
057.
final

int
childrenCount = mChildrenCount;

058.
if

(childrenCount !=
0
) {

059.
// Find a child that can receive the event.

060.
// Scan children from front to back.

061.
final

View[] children = mChildren;

062.
final

float
x = ev.getX(actionIndex);

063.
final

float
y = ev.getY(actionIndex);

064.
 
065.
final

boolean
customOrder = isChildrenDrawingOrderEnabled();

066.
for

(
int
i = childrenCount -
1
; i >=
0
; i--) {

067.
final

int
childIndex = customOrder ?

068.
getChildDrawingOrder(childrenCount, i) : i;

069.
final

View child = children[childIndex];

070.
if

(!canViewReceivePointerEvents(child)

071.
|| !isTransformedTouchPointInView(x, y, child,
null
)) {

072.
continue
;

073.
}

074.
 
075.
newTouchTarget = getTouchTarget(child);

076.
if

(newTouchTarget !=
null
) {

077.
// Child is already receiving touch within its bounds.

078.
// Give it the new pointer in addition to the ones it is handling.

079.
newTouchTarget.pointerIdBits |= idBitsToAssign;

080.
break
;

081.
}

082.
 
083.
resetCancelNextUpFlag(child);

084.
if

(dispatchTransformedTouchEvent(ev,
false
, child, idBitsToAssign)) {

085.
// Child wants to receive touch within its bounds.

086.
mLastTouchDownTime = ev.getDownTime();

087.
mLastTouchDownIndex = childIndex;

088.
mLastTouchDownX = ev.getX();

089.
mLastTouchDownY = ev.getY();

090.
newTouchTarget = addTouchTarget(child, idBitsToAssign);

091.
alreadyDispatchedToNewTouchTarget =
true
;

092.
break
;

093.
}

094.
}

095.
}

096.
 
097.
if

(newTouchTarget ==
null
&& mFirstTouchTarget !=
null
) {

098.
// Did not find a child to receive the event.

099.
// Assign the pointer to the least recently added target.

100.
newTouchTarget = mFirstTouchTarget;

101.
while

(newTouchTarget.next !=
null
) {

102.
newTouchTarget = newTouchTarget.next;

103.
}

104.
newTouchTarget.pointerIdBits |= idBitsToAssign;

105.
}

106.
}

107.
}

108.
 
109.
// Dispatch to touch targets.

110.
if

(mFirstTouchTarget ==
null
) {

111.
// No touch targets so treat this as an ordinary view.

112.
handled =dispatchTransformedTouchEvent(ev, canceled,
null
,

113.
TouchTarget.ALL_POINTER_IDS);

114.
}
else
{

115.
// Dispatch to touch targets, excluding the new touch target if we already

116.
// dispatched to it.  Cancel touch targets if necessary.

117.
TouchTarget predecessor =
null
;

118.
TouchTarget target = mFirstTouchTarget;

119.
while

(target !=
null
) {

120.
final

TouchTarget next = target.next;

121.
if

(alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {

122.
handled =
true
;

123.
}
else
{

124.
final

boolean
cancelChild = resetCancelNextUpFlag(target.child)

125.
|| intercepted;

126.
if

(dispatchTransformedTouchEvent(ev,cancelChild,

127.
target.child, target.pointerIdBits)) {

128.
handled =
true
;

129.
}

130.
if

(cancelChild) {

131.
if

(predecessor ==
null
) {

132.
mFirstTouchTarget = next;

133.
}
else
{

134.
predecessor.next = next;

135.
}

136.
target.recycle();

137.
target = next;

138.
continue
;

139.
}

140.
}

141.
predecessor = target;

142.
target = next;

143.
}

144.
}

145.
 
146.
// Update list of touch targets for pointer up or cancel, if needed.

147.
if

(canceled

148.
|| actionMasked == MotionEvent.ACTION_UP

149.
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

150.
resetTouchState();

151.
}
else
if
(split && actionMasked == MotionEvent.ACTION_POINTER_UP) {

152.
final

int
actionIndex = ev.getActionIndex();

153.
final

int
idBitsToRemove =
1

<< ev.getPointerId(actionIndex);

154.
removePointersFromTouchTargets(idBitsToRemove);

155.
}

156.
}

157.
 
158.
if

(!handled && mInputEventConsistencyVerifier !=
null
) {

159.
mInputEventConsistencyVerifier.onUnhandledEvent(ev,
1
);

160.
}

161.
return

handled;

162.
}


可以看到标红的有两句(intercepted = onInterceptTouchEvent(ev); if (!canceled && !intercepted) ),它会先调用 intercepted = onInterceptTouchEvent(ev);然后通过if判断。

view sourceprint?

1.
public

boolean
onInterceptTouchEvent(MotionEvent ev) {

2.
return

false
;

3.
}


它就一句话,默认false。也就是说这个谋士默认的意见是,永远不拦截!!!!只要有孩子,就交给孩子们处理吧。下面给出实例说明,新建TestLinearLayout继承自Linearlayout。

view sourceprint?

01.
package

org.yanzi.ui;

02.
 
03.
import

android.content.Context;

04.
import

android.util.AttributeSet;

05.
import

android.util.Log;

06.
import

android.view.MotionEvent;

07.
import

android.widget.LinearLayout;

08.
 
09.
public

class
TestLinearLayout
extends

LinearLayout{

10.
private

final
static
String tag =
"yan"
;

11.
public

TestLinearLayout(Context context, AttributeSet attrs) {

12.
super
(context, attrs);

13.
// TODO Auto-generated constructor stub

14.
}

15.
 
16.
@Override

17.
public

boolean
dispatchTouchEvent(MotionEvent ev) {

18.
// TODO Auto-generated method stub

19.
switch
(ev.getAction()){

20.
case

MotionEvent.ACTION_DOWN:

21.
Log.i(tag,
"TestLinearLayout-dispatchTouchEvent-ACTION_DOWN..."
);

22.
break
;

23.
case

MotionEvent.ACTION_UP:

24.
Log.i(tag,
"TestLinearLayout-dispatchTouchEvent-ACTION_UP..."
);

25.
break
;

26.
default
:
break
;

27.
}

28.
return

super
.dispatchTouchEvent(ev);

29.
}

30.
 
31.
@Override

32.
public

boolean
onInterceptTouchEvent(MotionEvent ev) {

33.
// TODO Auto-generated method stub

34.
switch
(ev.getAction()){

35.
case

MotionEvent.ACTION_DOWN:

36.
Log.i(tag,
"TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN..."
);

37.
break
;

38.
case

MotionEvent.ACTION_UP:

39.
Log.i(tag,
"TestLinearLayout-onInterceptTouchEvent-ACTION_UP..."
);

40.
break
;

41.
default
:
break
;

42.
}

43.
return

super
.onInterceptTouchEvent(ev);

44.
}

45.
 
46.
@Override

47.
public

boolean
onTouchEvent(MotionEvent event) {

48.
// TODO Auto-generated method stub

49.
switch
(event.getAction()){

50.
case

MotionEvent.ACTION_DOWN:

51.
Log.i(tag,
"TestLinearLayout-onTouchEvent-ACTION_DOWN..."
);

52.
break
;

53.
case

MotionEvent.ACTION_UP:

54.
Log.i(tag,
"TestLinearLayout-onTouchEvent-ACTION_UP..."
);

55.
break
;

56.
default
:
break
;

57.
}

58.
return

super
.onTouchEvent(event);

59.
}

60.
 
61.
 
62.
}


布局文件改成:

view sourceprint?

01.
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"

02.
xmlns:tools=
"http://schemas.android.com/tools"

03.
android:layout_width=
"match_parent"

04.
android:layout_height=
"match_parent"

05.
android:paddingBottom=
"@dimen/activity_vertical_margin"

06.
android:paddingLeft=
"@dimen/activity_horizontal_margin"

07.
android:paddingRight=
"@dimen/activity_horizontal_margin"

08.
android:paddingTop=
"@dimen/activity_vertical_margin"

09.
tools:context=
".MainActivity"

>

10.
 
11.
<TextView

12.
android:layout_width=
"wrap_content"

13.
android:layout_height=
"wrap_content"

14.
android:text=
"@string/hello_world"

/>

15.
 
16.
<org.yanzi.ui.TestLinearLayout

17.
android:id=
"@+id/linearlayout_test"

18.
android:layout_width=
"200dip"

19.
android:layout_height=
"200dip"

>

20.
 
21.
<org.yanzi.ui.TestButton

22.
android:id=
"@+id/btn_test"

23.
android:layout_width=
"wrap_content"

24.
android:layout_height=
"wrap_content"

25.
android:text=
"测试按钮"

/>

26.
</org.yanzi.ui.TestLinearLayout>

27.
 
28.
</RelativeLayout>


在Activity里给这个自定义LinearLayout也注册上onClick监听、onTouch监听。

view sourceprint?

01.
testLinelayout = (TestLinearLayout)findViewById(R.id.linearlayout_test);

02.
testLinelayout.setOnTouchListener(
new

View.OnTouchListener() {

03.
 
04.
@Override

05.
public

boolean
onTouch(View v, MotionEvent event) {

06.
// TODO Auto-generated method stub

07.
switch
(event.getAction()){

08.
case

MotionEvent.ACTION_DOWN:

09.
Log.i(tag,
"testLinelayout-onTouch-ACTION_DOWN..."
);

10.
break
;

11.
case

MotionEvent.ACTION_UP:

12.
Log.i(tag,
"testLinelayout-onTouch-ACTION_UP..."
);

13.
break
;

14.
default
:
break
;

15.
 
16.
}

17.
return

false
;

18.
}

19.
});

20.
 
21.
testLinelayout.setOnClickListener(
new

View.OnClickListener() {

22.
 
23.
@Override

24.
public

void
onClick(View v) {

25.
// TODO Auto-generated method stub

26.
Log.i(tag,
"testLinelayout---onClick..."
);

27.
}

28.
});


不复写事件传递里的 任何方法,流程如下: Line 57: 01-08 15:29:42.167 I/yan ( 5826): MainActivity-dispatchTouchEvent-ACTION_DOWN...

Line 59: 01-08 15:29:42.169 I/yan ( 5826): TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...

Line 61: 01-08 15:29:42.169 I/yan ( 5826): TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN...

Line 63: 01-08 15:29:42.169 I/yan ( 5826): TestButton-dispatchTouchEvent-ACTION_DOWN...

Line 65: 01-08 15:29:42.170 I/yan ( 5826): testBtn-onTouch-ACTION_DOWN...

Line 67: 01-08 15:29:42.170 I/yan ( 5826): TestButton-onTouchEvent-ACTION_DOWN...---------------------------------------------------------------------------------------------------------------------------

Line 69: 01-08 15:29:42.279 I/yan ( 5826): MainActivity-dispatchTouchEvent-ACTION_UP...

Line 71: 01-08 15:29:42.280 I/yan ( 5826): TestLinearLayout-dispatchTouchEvent-ACTION_UP...

Line 73: 01-08 15:29:42.283 I/yan ( 5826): TestLinearLayout-onInterceptTouchEvent-ACTION_UP...

Line 75: 01-08 15:29:42.287 I/yan ( 5826): TestButton-dispatchTouchEvent-ACTION_UP...

Line 81: 01-08 15:29:42.298 I/yan ( 5826): testBtn-onTouch-ACTION_UP...

Line 83: 01-08 15:29:42.301 I/yan ( 5826): TestButton-onTouchEvent-ACTION_UP...

Line 85: 01-08 15:29:42.313 I/yan ( 5826): testBtn---onClick...

由Activity的dispatchTouchEvent----Linearlayout的dispatchTouchEvent------------问问它的谋士要不要让孩子知道onInterceptTouchEvent---------孩子的dispatchTouchEvent-----孩子的onTouch监听------孩子的onTouchEvent----孩子的onClick监听。为了更清晰这个流程,下面作如下改动:1、如果事件传给了孩子们,但孩子没有onTouch和onClick监听怎么办?即将button的onclick和onTouch都注释掉:流程如下:
Line 131: 01-08 15:36:16.574 I/yan ( 6124): TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...

Line 133: 01-08 15:36:16.574 I/yan ( 6124): TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN...

Line 135: 01-08 15:36:16.574 I/yan ( 6124): TestButton-dispatchTouchEvent-ACTION_DOWN...

Line 137: 01-08 15:36:16.575 I/yan ( 6124): TestButton-onTouchEvent-ACTION_DOWN...

Line 143: 01-08 15:36:16.746 I/yan ( 6124): MainActivity-dispatchTouchEvent-ACTION_UP...

Line 145: 01-08 15:36:16.747 I/yan ( 6124): TestLinearLayout-dispatchTouchEvent-ACTION_UP...

Line 147: 01-08 15:36:16.747 I/yan ( 6124): TestLinearLayout-onInterceptTouchEvent-ACTION_UP...

Line 149: 01-08 15:36:16.748 I/yan ( 6124): TestButton-dispatchTouchEvent-ACTION_UP...

Line 151: 01-08 15:36:16.748 I/yan ( 6124): TestButton-onTouchEvent-ACTION_UP...

因为事件给了孩子们,它没监听也关系不到父亲了,父亲的onClick和onTouch都没执行。2,如果将TestLinearlayout的onInterceptTouchEvent 改成return true,即不让孩子们知道。 Line 57: 01-08 15:40:06.832 I/yan ( 6640): MainActivity-dispatchTouchEvent-ACTION_DOWN...

Line 59: 01-08 15:40:06.835 I/yan ( 6640): TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...

Line 61: 01-08 15:40:06.836 I/yan ( 6640): TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN...

Line 63: 01-08 15:40:06.836 I/yan ( 6640): testLinelayout-onTouch-ACTION_DOWN...

Line 65: 01-08 15:40:06.836 I/yan ( 6640): TestLinearLayout-onTouchEvent-ACTION_DOWN...

Line 67: 01-08 15:40:07.016 I/yan ( 6640): MainActivity-dispatchTouchEvent-ACTION_UP...

Line 69: 01-08 15:40:07.017 I/yan ( 6640): TestLinearLayout-dispatchTouchEvent-ACTION_UP...

Line 73: 01-08 15:40:07.025 I/yan ( 6640): testLinelayout-onTouch-ACTION_UP...

Line 75: 01-08 15:40:07.026 I/yan ( 6640): TestLinearLayout-onTouchEvent-ACTION_UP...

Line 77: 01-08 15:40:07.052 I/yan ( 6640): testLinelayout---onClick...

果然事件就此打住,孩子们压根不知道,父亲执行了onClick和onTouch监听。可见父亲还是伟大的啊,只要谋士不拦截事件,那么事件就给孩子。 最后的结论:1、如果是自定义复合控件,如图片+文字,我再Activity里给你注册了onClick监听,期望点击它执行。那么最简单的方法就是将图片+文字的父布局,也即让其容器ViewGroup的秘书将事件拦下,这样父亲就可以执行onClick了。这时候的父亲就像一个独立的孩子一样了(View),无官一身轻,再也不用管它的孩子了,可以正常onClick onTouch.2、如果希望一个View只onTouch而不onClick,在onTouch里return
true就ok了。3、dispatch是为了onTouch监听,onTouchEvent是为了onClick监听。4、自定义布局时,一般情况下:@Override

public boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}@Override

public boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);

我们可以复写,但是最后的super.***是万万不能少滴。如果少了,表示连dispatch*** onTouchEvent压根就不调用了,事件就此打住。 貌似真相水落石出了,但究竟清楚了没有请看下篇根据自定义复合控件的监听问题再探讨下。

参考: 链接1 (灰常感谢前辈的大作啊) 链接2测试代码: http://www.it165.net/uploadfile/files/2014/0403/ href="http://www.it165.net/pro/ydad/" target=_blank>Androidsjcd.zip
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: