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