事件分发,事件处理,事件拦截是一个比较蛋疼的地方,对于View的话,比ViewGroup相对要好些,View是没有事件拦截的,只有事件分发和处理;首先先看下正常情况下View的事件分发和处理会怎么样,在下面这三段代码中各打印一段log,看在touch的时候有哪些会打印出来;
自定义view的onTouchEvent:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("TAG","onTouchEvent--->"+event.getAction());
return super.onTouchEvent(event);
}
自定义view的OnTouchListener监听:
touchView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("TAG","onTouch--->"+event.getAction());
return false;
}
});
自定义view的点击事件:
touchView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG","onClick--->");
}
});
在touch完毕后,会看到
结果一:
onTouch(DOWN)—>onTouchEvent(DOWN)—>onTouch(MOVE)—>onTouchEvent(MOVE)—>onTouch(UP)—>onTouchEvent(UP)—>onClick
将自定义view的setOnTouchListener的返回值改成true;
touchView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("TAG","onTouch--->"+event.getAction());
return true;
}
});
结果只有一个地方的log被打印出来了;
没错,不管你怎么touch只会打印onTouch处的log,想知道为什么,就一起去看看view的源码,在view源码中关于事件分发和处理有两个方法比较重要:
dispatchTouchEvent 事件分发
onTouchEvent 处理用户手势(DOWN,MOVE,UP等)
先看下dispatchTouchEvent 方法
/**
* 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 (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
这里看到有个ListenerInfo ,点进去看下;
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
protected OnContextClickListener mOnContextClickListener;
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
就是将view里面的一些事件全部放到了一个类中,看到dispatchTouchEvent中的这个if判断:
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
如果ListenerInfo不为空,ListenerInfo中的OnTouchListener不为空,且Enabled为true;li.mOnTouchListener.onTouch(this, event)这里为true则result = true;否则result = false;
先说result = false的情况:
走完这个if,就会走到下面这if里面:
if (!result && onTouchEvent(event)) {
result = true;
}
result = false,!result就会true,就会去走onTouchEvent(event),
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
...
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
在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);
return result;
}
在该方法中就会调用ListenerInfo中的OnClickListener,这样就会走到点击事件,也就是下面这个结果:
onTouch(DOWN)—>onTouchEvent(DOWN)—>onTouch(MOVE)—>onTouchEvent(MOVE)—>onTouch(UP)—>onTouchEvent(UP)—>onClick
result = true的情况:
resutl要为true的话,
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
上面这个if中的li.mOnTouchListener.onTouch(this, event)返回值就要为true,也就是
touchView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("TAG","onTouch--->"+event.getAction());
return true;
}
});
返回值为true;这个时候resutl为true,!result就会为false,那么下面这个代码就不会走,
if (!result && onTouchEvent(event)) {
result = true;
}
!result为false,onTouchEvent(event)就不会调用,并将result返回回去,onTouchEvent(event)没有被调用,performClick()肯定也就不会被调用了,所以不管你怎么touch都只会打印onTouch里面的log;
再看看自定义view中的onTouchEvent和dispatchTouchEvent这两个方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("TAG","onTouchEvent--->"+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
将onTouchEvent的返回值改为ture,运行看看什么效果;
并没有调用点击事件,这是因为返回true并没有去继承父类,不会去走父类中的onTouchEvent了,dispatchTouchEvent返回值改成true也是差不多的。上面如有写的不对的地方,欢迎交流。