android View事件分发流程
android 中的view虽然不是四大组件,但是同样也是相当重要的。不论是我们在平时自定义控件还是面试的时候总会遇到一些关于view点击事件分发的一些问题。接下来就让我给大家分享一下关于view的事件分发流程。
要想了解view的事件分发首先我们要知道什么是事件分发?所谓点击事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递到具体的View,而这个传递的过程就是事件分发的过程。点击事件的分发过程有三个很重要的方法共同完成:dispatchTouchEvent、onInterceptTouchEvent、和onTouchEvent,下面我们先介绍一些这几个方法:
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,用来判断是佛偶拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会再次被调用了,返回结果表示是否拦截当前事件,true表示拦截。
public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消费当前时间,如果不消费,在同一个时间序列当中,当前View无法再次接收到事件。
上述的三个方法到底有什么关系呢?我们可以用下面的伪代码来描述他们之间的关系。
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInTerceptTouchEvent(ev)){
consume= ontouchEvent(ev);
}else{
consume=child.dispatchTouchEvent(ev);
}
return consume;
}
这一段伪代码可以说是吧他们之间的关系表达的淋漓尽致。
由于ViewGroup的事件分发流程稍微有点复杂我们放到下一篇文章单独介绍,下一篇我们主要介绍一下点击事件是怎么从顶级View一步一步下发的。
接下来我们就共同来分析一下View点击事件的下发。这里的View不包含ViewGroup。接下来让我们先看一下View的dispatchTouchEvent方法,如下所示
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
...
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;
}
View对点击事件的处理过程就比较简单了,因为View(不包含ViewGroup)是一个单独的元素,它没有子元素因此无法向下传递事件,所以只能自己处理事件。
从上面的源码可以看出View对点击事件的处理过程,首先会判断view有没有甚至OnTouchListener事件,若设置了ontouch事件并且onTouch方法的返回值为true,那么dispatchTouchEvent直接返回true不会调用View的onTouchEvent方法,可见onTouchListener的优先级高于onTouchEvent,这样的好处是外界可以很方便的处理点击事件。
接下来在分析一下onTouchEvent的实现。首先让我们看一下view不可用状态下点击事件的处理。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
从这段代码可以看出view只要clickable是true那么他就是可点击的。无论这个view可不可用都是会消费这个点击事件的。
接着往下看,如果view设置了代理,那么他就会直接调用代理的onTouchEvent的方法,里面的处理流程与view的差不多。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
接下来我们看一下view的onTouchEvent对点击事件的具体处理,如下所示:
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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) {
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) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
}
...
}
break;
}
return true;
}
return false;
从上面的代码我们可以看出只要CLICKABLE与LONG_CLICKABLE有一个为true那么这个view就会消费掉这个事件,即onTouchEvent的返回值为true。即使这个view是DISABLE的也会消费掉这个事件,上面我们已经分析过了。然后就是当MotionEvent.ACTION_UP事件触发时就会触发performClick()方法,在这个方方法中如果view 设置了 onClickListener那么就会调用onClick方法,也就是我们常常给view设置点击事件的回调方法
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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
View的LONG_CLICKABLE属性默认是false,而View的CLICKABLE是true还是false就不一定了,主要看这个view是否是可点击的,如果是可点击的那么CLICKEABLE自然就是true,反之如果不可点击那么CLICKABLE就是false。注意,在我们设置点击事件监听的时候会自动的把CLICKABLE置成true,长按点击事件也是如此,从源码我们就可以看出 如下:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
到这里我们对View的点击事件的分发机制就分析完毕了。如果有不对的地方希望大家多多指正。
来源:CSDN
作者:guojingbu
链接:https://blog.csdn.net/guojingbu/article/details/82598022