让我自己看源码是看不懂的,还好有这么多大神。
1. dispatchTouchEvent
ViewGroup 在分发事件时会调用子 View 的 dispatchTouchEvent 方法。
对于 View (非 ViewGroup)来说,dispatchTouchEvent 方法被理解为 "handleEvent" 可能会更会合适。
该方法的返回值表示 Event 是否被 View 消费了,true 表示消费了。
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("MyView", "dispatchTouchEvent start" );
boolean result = super.dispatchTouchEvent(event);
Log.e("MyView", "dispatchTouchEvent end " + result);
return result;
}
}
点击后日志如下:
03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent start
03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
基本 View 并没有消费掉 Event。
2. dispatchTouchEvent 源码
来看看 View 是如何 handle event 的,重点的部分可能就这么点,源码是 6.0 版本的。
boolean result = false;
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;
}
return result;
3. OnTouchListener
findViewById(R.id.myView).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("MyView", "onTouchListener onTouch start");
Log.e("MyView", "onTouchListener onTouch end " + false); // 不消费事件
return false;
}
});
点击后日志如下:
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch start
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
findViewById(R.id.myView).setEnabled(false);
那么 OnTouch 事件不会被触发。
4. onTouchEvent
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
注释上说,disabled 的 View 依旧会消费事件,即使它什么也没做。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
假如设置了 TouchDelegate ,那么由它来负责。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
// 之后分析
return true;
}
return false;
如果 View 是 clickable 或者 long_clickable 的,则消费事件。
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch start
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent start
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent end false
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
默认的 View 是不可点击的,所以事件还没被消费。
5. OnLongClickListener
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
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);
}
break;
private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
这里 post 了一个 500 毫秒的延时事件。
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
如果 500 毫秒后 View 依旧是 pressed 状态,则执行 performLongClick 函数,并将返回结果保存在 mHasPerformedLongPress 中。
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
在 performLongClick 函数中 OnLongClick 事件被触发。长按日志如下:
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent start
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent end true
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true
03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start
03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false
长按事件触发距离事件分发结束相差约 500 毫秒。
6. OnClickListener
case MotionEvent.ACTION_UP:
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;
如果 OnLongClickListener 被执行且返回 true,那么 perfermClick 函数不会执行。
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);
return result;
}
长按日志如下:
03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start
03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent end true
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick end false
这里有个小细节,当 Down event(0 和 1 分别代表 Down 和 Up) 被 View 消费后。
View 才会收到对应的 Up event,这就涉及到 ViewGroup 是如何分发事件了。
推荐:
http://blog.csdn.net/dmk877/article/details/48781845