Android-事件分发机制
讲讲Android的事件分发机制
一般情况下,一个事件流的产生基本上都是从用户的ACTION_DOWN开始和后续的ACTION_MOVE(多个)、ACTION_UP以及ACTION_CACEL,这些事件形成了一个事件流的过程。
这里会涉及到三个非常重要的方法
-
dispatchTouchEvent()
-
onInterceptTouchEvent()
-
onTouchEvent()
当用户点击屏幕的那一刻,事件首先会传递到当前的Activity,会调用Activity的dispatchTouchEvent()方法
getWindow().superDispatchTouchEvent(ev),而window是PhoneWindow, mDecorView.dispatchTouchEvent()
DecorView是根布局,里面调用了super.dispatchTouchEvent(),而decorView继承FrameLayout,且FrameLayout是ViewGroup的子类,所以最终会传递到ViewGroup的dispatchTouchEvent()
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
上面这段代码非常重要,intercept
代码是否要拦截事件的向下分发,而影响intercept
的因数有actionMasked == MotionEvent.ACTION_DOWN
用户按下操作的事件
还有mFirstTouchTarget != null
这个mFirstTouchTarget
是在子View能处理事件后才赋值的
(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
是另外一个很重要的方法,写过自定义View并且遇到事件冲突的同学一定知道getPatent().requestDisallowInterceptTouchEvent(value:Boolean)
这个值会影响到if (!disallowIntercept) {
是否让父View去拦截事件的入口条件,但是父容器是不是需要拦截,还得需要看onInterceptTouchEvent()
有没有重写拦截的逻辑,默认是不拦截的 ,当然系统的控件比如ViewPager帮我们实现了事件冲突的处理。
那intercept
接下来做了什么处理呢
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
.......
//遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
//判断当前点击的xy是否在当期child的区域上,如果不在当然就不用去处理事件
//接着下一次遍历
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//这里是处理当前child是否有能力处理事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//有能力处理后,将mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//赋值后续事件
alreadyDispatchedToNewTouchTarget = true;
}
上面的流程是有子View的情况,最后还是会执行到下面的else,因为alreadyDispatchedToNewTouchTarget赋值为true ,所以最后会将handled返回到上层,代表消费了事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}
}
}
以上都是ACTION_DOWN的事件分发和事件处理的逻辑,接下来就是ACTION_MOVE的事件继续传递,当然事件最终会传递到View的dispatchTouchEvent,这里我们最后去分析。
当用户手指在屏幕上滑动,这里的第二个事件就会传递来,这里的事件类型为ACTION_MOVE
事件还是会传递到ViewGroup的dispatchTouchEvent
里面来
if (actionMasked == MotionEvent.ACTION_DOWN) {
//清除变量等标识
cancelAndClearTouchTargets(ev);
resetTouchState();
}
因为这一次是MOVE事件,所以这里的mFirstTarget不会被重新置为null的,还是属于一个事件流里面,由于mFirstTouchTarget
不为null,所以还是会去看看父容器是不是要去拦截事件,假如还是默认不拦截,那么intercepted
还是为false
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}
由于intercepted为false还是会进入if里面,但是由于是MOVE事件,所以事件是不会继续向下分发的
if (!canceled && !intercepted) {
//不会执行
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
最后还是会执行到dispatchTransformedTouchEvent,这里需要注意的是child参数传递的是target.child,这里的target就是在addTouchTarget里面赋值的
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
这里最后将handled = true 返回说明move事件处理事件了 当然会有多个,一般来说是第二个move事件才会确定move的事件处理逻辑,这里我们可以做一些滑动冲突的拦截,来解决滑动冲突
最后我们看一下这个dispatchTransformedTouchEvent
方法,这个是很重要的,里面判断是事件的流向,如果有child就会传递到子View,如果子View是一个ViewGroup的话,会继续之前的流程分发,如果是View的话,就会看View会不会处理消费事件,有没有发现,事件分发的传递其实就是一个递归。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {」
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
}
如果child为null,就要看当前View是否能处理事件,能处理就消费事件了,不为null,就会看下一层view是否能处理消费事件
接下来就会切换类了
View.java
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//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;
}
这里我们看到li != null
&&li.mOnTouchListener != null
&&(mViewFlags & ENABLED_MASK) == ENABLED
&&li.mOnTouchListener.onTouch(this, event)
缺一不可的条件result才是true代表事件消费了,这里也是我们设置setOnTouchListener的就会赋值到这里面,所以onTouch事件是优先于onTouchEvent事件了,并且返回值最终会影响onTouchEvent事件会不会执行
只有onTouch方法返回false,onTouchEvent才会被执行
public boolean onTouchEvent(MotionEvent event) {
UP事件
if (!post(mPerformClick)) {
performClickInternal();
}
}
private boolean performClickInternal() {
return 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;
}
一个事件的结束必然是手指离开屏幕的时候,除异常cacel事件外,所以我们在ACTION_UP里面找到li != null
&& li.mOnClickListener != null
这里指的是我们设置的点击事件,如果点击事件做了事情那么result就会被返回,代表事件消费了
这里我们可以看到onTouch
>onTouchEvent
>onClick
最终事件的返回变量会回到Acitvity的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
如果事件消费了 if (getWindow().superDispatchTouchEvent(ev))为ture
就不会执行的onTouchEvent,事件分发结束,否者传递到activity的onTouchEvent
如果activity的onTouchEvent也没有消费事件,这个事件就会被丢弃,整个事件分发结束
到这里我们的分析就基本结束了
如果我们遇到事件冲突,就可以考虑通过onInterceptTouchEvent
和dispatchTouchEvent
以及requestDiaAllowInterceptTouchEvent()
来通过滑动距离以及具体逻辑来拦截和处理冲突事件