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

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

2015-06-25 11:01 711 查看
这是在其他网站看到的一篇文章,感觉写得很好,转载过来,方便以后复习。非常感谢作者的分享,原文地址:http://www.it165.net/pro/html/201404/11500.html

本文背景:前些天用到了之前写的自定义图片文字复合控件,在给他设置监听时遇到了麻烦。虽然最后解决了问题,但发现在不重写LinearLayout的onInterceptTouchEvent时,子ImageView、子TextView、父Linearlayout三者不同的属性配置(android:clickable android:focuseable)会造成自定义控件onClick监听失败、或成功。复写了父Linearlayout 的onInterceptTouchEvent时,监听不受子图片、子文字的属性影响。为知其所以然,深入研究android的事件传递机制,记录于此。


一、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的源码比较多,贴最重要的:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: