写个Demo,分别调用一个Button的onTouch和onClick函数:
bt1 = (Button) findViewById(R.id.bt1);
bt1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch,event action:" + event.getAction());
return false;
}
});
bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick...");
}
});
在手机上做点击操作,结果如下:
01-16 06:41:54.078 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:0
01-16 06:41:54.173 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:1
01-16 06:41:54.217 7507-7507/com.acxingyun.touchevent I/TouchActivity: onClick...
0代表按下,1代表抬起:
后面还有拖动等其它定义:
试试拖动操作:
01-16 06:49:16.708 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:0
01-16 06:49:16.833 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:16.848 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:16.865 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:16.899 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:16.956 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:16.987 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:2
01-16 06:49:17.056 7507-7507/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:1
01-16 06:49:17.088 7507-7507/com.acxingyun.touchevent I/TouchActivity: onClick...
action_down后,就是一连串的action_move,最后action_up,就是点击,拖动,然后抬起。
改下代码,onTouch返回true:
bt1 = (Button) findViewById(R.id.bt1);
bt1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch,event action:" + event.getAction());
return true;
}
});
bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick...");
}
});
结果:
01-16 07:12:19.293 9767-9767/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:0
01-16 07:12:19.355 9767-9767/com.acxingyun.touchevent I/TouchActivity: onTouch,event action:1
为啥没有再回调onClick了?
Andorid中,任何一个控件被点击后,都会调用该控件的dispatchTouchEvent,Button没有这个方法,但依次网上查找,从父类TextView到View,终于找到了这个方法,在这个方法里摘取部分代码:
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
mOnTouchListener是在设置监听的时候就赋值了,view默认是ENABLED的,即(mViewFlags & ENABLED_MASK) == ENABLED,mOnTouchListener.onTouch(this, event)意思是调用mOnTouchListener的onTouch函数,这个函数是在设置监听的时候就override了:
bt1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch,event action:" + event.getAction());
return true;
}
});
如果override返回true,一系列条件成立,resule = true,就不会进入下面onTouchEvent了:
if (!result && onTouchEvent(event)) {
result = true;
}
再看看onTouch的源码:
当Touch事件分配到了一个view上调用,如果监听消费了这个touch事件,返回true。所以一旦override了控件的onTouch,返回true,表示onTouch事件被消费了,不会再进入onTouchEvent,而onClick正是在onTouchEvent中:
public boolean onTouchEvent(MotionEvent event) {
..................
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
MotionEvent.ACTION_UP里,执行了performClick():
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
在li.mOnClickListener.onClick(this);这里,回调了View的onClick函数。
有个疑问,一系列的操作,action_down,action_move,和action_up是如何相继触发的???
ViewGroup的事件分发机制:
ViewGroup继承View,是多个View的集合,LinearLayout、RelelaytiveLayout这些布局都是继承自ViewGroup。
之前说过,点击一个View时首先会调用它自身的dispatchTouchEvent,当我们点击一个View时,其实是先调用它的父布局,也就是ViewGroup的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) {
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;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
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--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
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);
}
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)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
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;
}
return target.dispatchTouchEvent(ev);
}
进来后,如果是action_down,先把mMotionTarget置为null,然后做个判断:
if (disallowIntercept || !onInterceptTouchEvent(ev)){
......
}
disallowIntercept表示是否拦截点击事件,默认false,就进入了后面:
onInterceptTouchEvent{
return false;
}
返回false,就进入里面的逻辑:遍历ViewGroup中的childView,如果发现某个childView是当前点击的view,调用该view的dispatchTouchEvent:
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
child.dispatchTouchEvent(ev)返回true,则退出这个ViewGroup的dispatchTouchEvent;而child.dispatchTouchEvent(ev)又会进入到这个view的onTouch中,只要是clickable的,child.dispatchTouchEvent都会返回true;不是clickable的,child.dispatchTouchEvent返回false,如果所有child.child.dispatchTouchEvent返回false,继续往下执行:
final View target = mMotionTarget;
if (target == null) {
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);
}
因为mMotionTarget为null,所以这里进入super.dispatchTouchEvent(ev),因为ViewGroup继承自View,这里就进入了View.dispatchTouchEvent,就是执行这个View自身的点击时间流程,就和普通View的点击流程一样了。
ViewGroup的事件分发流程:
View的事件分发流程: