btnTest.setOnTouchListener( new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System. out.println( "btnTest onTouch "+event.getAction());
return false;
}
});
ivLanucher.setOnTouchListener( new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System. out.println( "ivLanucher onTouch "+event.getAction());
return false;
}
});
btnTest.setOnTouchListener( new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System. out.println( "btnTest onTouch "+event.getAction());
return true;
}
});
ivLanucher.setOnTouchListener( new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System. out.println( "ivLanucher onTouch "+event.getAction());
return true;
}
});
①当我们点击ImageView时的日志如下
package com.example.dispatchtoucheventpractice;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent. ACTION_DOWN:
System. out.println( "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent. ACTION_MOVE:
System. out.println( "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent. ACTION_UP:
System. out.println( "dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent. ACTION_DOWN:
System. out.println( "onTouchEvent ACTION_DOWN");
break;
case MotionEvent. ACTION_MOVE:
System. out.println( "onTouchEvent ACTION_MOVE");
break;
case MotionEvent. ACTION_UP:
System. out.println( "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
MainActivity的代码如下
btnTest.setOnTouchListener( new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action=event.getAction();
switch (action) {
case MotionEvent. ACTION_DOWN:
System. out.println( "onTouch ACTION_DOWN");
break;
case MotionEvent. ACTION_MOVE:
System. out.println( "onTouch ACTION_MOVE");
break;
case MotionEvent. ACTION_UP:
System. out.println( "onTouch ACTION_UP");
break;
}
return false;
}
});
我们点击按钮并移动一小小段距离打印结果如下
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent (MotionEvent event) {
if (mOnTouchListener != null && ( mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch( this, event)) {
return true;
}
return onTouchEvent(event);
}
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
/**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
/**
* 如果一个View是disabled, 并且该View是Clickable或者longClickable,
* onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件
*/
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
/**
* 如果此View有触碰事件处理代理,那么将此事件交给代理处理
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/**
* 如果不可点击(既不能单击,也不能长按)则直接返回false
*/
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
/**
* 是否需要获得焦点及用变量focusTaken设置是否获得了焦点.
* 如果我们还没有获得焦点,但是我们在触控屏下又可以获得焦点,那么则请求获得焦点
*/
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
/**
* 判断是否进行了长按事件的返回值情况,如果为false则移除长按的延迟消息并继续往下执行
*/
if (!mHasPerformedLongPress) {
// 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) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
/**
* ViewConfiguration.getPressedStateDuration() 获得的是按下效
* 果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125毫秒
*/
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
/**
* 给mHasPerformedLongPress设置初始值为false
*/
mHasPerformedLongPress = false;
/**
* 发送一个延迟消息延迟时间为ViewConfiguration.getTapTimeout()在2.2的源码中此值为115毫秒
* 到达115毫秒后会执行CheckForTap()方法,如果在这115毫秒之间用户触摸移动了,则
* 删除此消息.否则执行按下状态,在CheckForTap()中检查长按.
*/
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
/**
* 当手指在View上面滑动超过View的边界,
*/
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
/**
* 如果手指滑动超过Vie的边界则移除DOWN事件中设置的检测
*/
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
下面我们来拆分一下上面的源码首先执行一个if判断语句
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ));
}
public boolean onTouchEvent(MotionEvent event) {
。。。。。。。。。。。
此处有省略
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
。。。。。。。。。。。
此处有省略
}
return true;
}
return false;
}
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ));
}
在上面的注释中已经对其进行了说明,这里单独拿出来再强调一下-----如果一个View是disabled, 并且该View是Clickable或者longClickable, onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件,这一点从上面的代码可以看出,
如果一个enabled的View,并且是clickable或者longClickable的,onTouchEvent()会执行下面的代码逻辑并返回true,这一点从上面的省略代码片段可以得出。
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
在这个方法中首先
给mPrivateFlags设置一个PREPRESSED的标识,然后设置为mHasPerformedLongPress设置一个初始值false,接着会执行一个延迟
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
在这里ViewConfiguration.getTapTimeout()的值为115毫秒(注意以上源码包括时间常量都是2.2源码中,其他源码可能会稍有不同)这个延迟有什么作用呢?
在给定的TapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动.具体的做法是,将 CheckForTap的实例mPendingCheckForTap添加时消息队例中,延迟执行。如果在这tagTimeout之间用户触摸移动了,则删除此消息.否则执行按下状态.然后检查长按。
/**
* ACTION_DOWN事件延迟115毫秒后调用
*/
private final class CheckForTap implements Runnable {
public void run() {
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState();
/**
* 如果View支持长按事件即View是LONG_CLICKABLE的则发送一个长按事件的检测
*/
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick(ViewConfiguration.getTapTimeout());
}
}
}
/**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
*
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
*/
public void setOnLongClickListener (OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable( true);
}
mOnLongClickListener = l;
}
从源码中我们可以看到设置了OnLongClickListener后如果这个View不是LONG_CLICKABLE的,那么就把它设置成LONG_CLICKABLE的。这样我们回到CheckForTap方法在View是LONG_CLICKABLE的情况下就会调用postCheckForLongClick方法,这个方法的源码如下
private void postCheckForLongClick(int delayOffset) {
/**
* 设置mHasPerformedLongPress为false表示长按事件还未触发
*/
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
/**
* 此delayOffset是从上面的CheckForTap类中传过来的值为ViewConfiguration.getTapTimeout()
* ViewConfiguration.getLongPressTimeout()在2.2中为500毫秒,也就是经过500-115毫秒后会执行CheckForLongPress方·· * 法在CheckForLongPress方法中会调用执行长按事件的方法,由于在ACTION_DOWN事件中有一个延迟消息延迟115毫秒后 * 执行CheckForTap中的run方法所以这里500-115+115=500也就是说从按下起经过500毫秒会触发长按事件的执行
*/
postDelayed(mPendingCheckForLongPress,ViewConfiguration.getLongPressTimeout() - delayOffset);
}
在上面的方法会有一个延迟经过500-115毫秒后会执行CheckForLongPress方法。
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
/**
* 因为等待形成长按的过程中,界面可能发生变化如Activity的pause及restart,这个时候,长按应当失效.
* View中提供了mWindowAttachCount来记录View的attach次数.当检查长按时的attach次数与长按到形成时.
* 的attach一样则处理,否则就不应该再当前长按. 所以在将检查长按的消息添加时队伍的时候,要记录下当前的windowAttach *Count.
*/
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
/**
* 执行长按事件后返回值为true,设置mHasPerformedLongPress为true此时会屏蔽点击事件
*/
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
/**
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu
* if the OnLongClickListener did not consume the event.
*
* @return True there was an assigned OnLongClickListener that was called, false
* otherwise is returned.
*/
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
/**
* 到了重点可以看到在这里会执行我们为View设置的长按事件的回调,这里的mOnLongClickListener就是我们自己给View设置的长按的监听,
* 从这里也可以得出一个结论即长按事件是在ACTION_DOWN中执行的
*/
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
终于来了个重点我们看到在其中有个判断
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
也就是说如果你设置了长按的监听,那么mOnLongClickListener!=null此时就会执行我们重写的onLongClick()方法,
这里我们也得出一个结论:
1、如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true;才把mHasPerformedLongPress置为ture;
public void setOnClickListener (OnClickListener l) {
if (!isClickable()) {
setClickable( true);
}
mOnClickListener = l;
}
看到没?当我们设置了onClickListener时如果isClickable()=false,就执行 setClickable(true)。
if (!mHasPerformedLongPress) {
// 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();
}
}
}
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
终于看到了,终于看到我们设置的监听执行了,可以看到在这里如果mOnclickListener!=null就会执行onClick(),这里的
mOnclickListener就是我们设置的监听从而我们可以得出一条结论:onClick()方法是在ACTION_UP中执行的。
btnClick.setOnClickListener( new OnClickListener() {
@Override
public void onClick(View v) {
Toast. makeText(MainActivity.this,"点击事件",0) .show();
}
});
btnClick.setOnLongClickListener( new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast. makeText(MainActivity.this,"长按事件",0).sh ow();
return false;
}
});
if (performLongClick()) {
mHasPerformedLongPress = true;
}
当
mHasPerformedLongPress为false的时候就是在ACTION_UP中执行performClick()方法从而执行点击事件
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
2.一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的
3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE
4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false
5.如果View设置了onTouchListener了,并且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()