前言
· 在Android自定义View/ViewGroup中,我们免不了出现它的触摸事件,所以了解View的事件分发非常重要
· 本人菜鸡水平,写此文章为了加深印象,如果发现错误或不足恳请指正。谢谢
讲在前面
在事件分发过程中,我们首先要知道View的事件分发是要区分View和ViewGroup
View的事件分发
view事件分发中的三个重要方法:
1.dispatchTouchEvent()
2.onTouch()
3.onTouchEvent()
·我看首先看看事件在View是如何传递和消费的
DispatchTouchEvent()部分代码,也是核心代码
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;
}
首先会判断View是否设置了触摸事件,如果设置了点击事件,并且并且当前View是可点击的,那么就会回调li.mOnTouchListener.onTouch(this, event)),如果此方法返回true,那么就不会调用onTouchEvent(event)这个方法了,说明这个触摸事件被消费了。如果返回false,那么就会回调onTouchEvent(event)这个方法,如果返回true,事件被销毁。返回false代表事件继续向下传递。由此可见执行优先级:onTouch() >onTouchEvent()。
然后我们看看onTouchEvent()这个方法
是switch case语句我们不分析,各种事件了。我们主要看看UP中:
case MotionEvent.ACTION_UP:
//......
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
//......
break;
我们在打开performClickInternal看看,最后就是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;
}
//......
return result;
}
通过源码我们可以看到,onClickListener.onClick()方法其实就是onTouchEvent中up事件中被调用,所以执行优先级:onTouchEvent() > onClick()。如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。View的事件分发就说到这。
ViewGroup的事件分发
view事件分发中的三个重要方法:
1.dispatchTouchEvent()
2.onInterceptTouchEvent()
3.onTouchEvent()
首先我们看看ViewGroup的dispatchTouchEvent()这个方法,源码太多,我们把核心的代码提出来
public boolean dispatchTouchEvent(MotionEvent ev){
//......
boolean handled = false;
final boolean intercepted;
intercepted=onInterceptTouchEvent(event);
if(intercepted)
handled =onTouchEvent(event)
else{
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) //找到当前触摸位置的childView
handled = child.dispatchTouchEvent(event);
}
//......
return handled;
}
这个就是ViewGroup的逻辑,首先先判断onInterceptTouchEvent(event)(默认为false)如果为true,则拦截这个事件。调用viewgroup的onTouchEvent(event)来判断是否消费事件。否则向下传递。遍历找到子View并把当前事件交给子View调用子View的dispatchTouchEvent(event)。
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
onInterceptTouchEvent()默认返回false,代表viewGroup不拦截事件
1.当事件传递到View时,如果View的onTouchEvent()返回false,则父类的onTouchEvent()会被调用,依次向上传递
2.若所有的View都不消耗事件时,Activity的onTouchEvent()会被调用
结论
1.对于View一旦决定拦截事件即onTouchEvent()返回true,那后续的整个事件序列都会交给它消耗;
2.执行优先级:onTouch() > onTouchEvent() > onClick()
3.事件的传递是
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
如果没有找到合适的View消费事件:那么事件反向传递如果最后 Activity 也没有处理,本次事件才会被抛弃
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
感谢阅读,欢迎点赞和评论