背景
在开发中,我们经常需要自定义android组件,而事件的处理是最重要的部分之一,当手指按下,拖动和释放,都经历了什么事件的处理,会达到怎样的效果,当滑动冲突时,我们需要怎么去解决问题,通过对事件分发的了解,我想,对于上面的问题,你都能迎刃而解。在android中主要两种组件,一是原始的View,例如:TextView,Button …… ,一是继承View的ViewGroup,例如:RelativeLayout,LinearLayout …… ,两者还是有些区别的。
View的事件分发
首先来看View的事件分发机制。
1.1 view事件分发图。
1.2 分发过程。
① 在手机屏幕上发生MotionEvent.DOWN 事件时,即手机在屏幕上按下时,底层将触摸事件传递给View的boolean dispatchTouchEvent(MotionEvent ev)方法,所有的事件都在这个方法里面开始进行分发处理。
② dispatchTouchEvent()有boolean类型的返回值,返回 true 则表示在此view中直接消费掉该事件,可以理解为,直接把事件“吃”了,谁也不给,就自己享用,所以它的下一级View是不会收到事件的(PS:此步骤具体运用是发生在ViewGroup中)。
③ dispatchTouchEvent()分发时,先判断该View是否有设置OnTouchListener监听器,如有,则回调OnTouchListener事件的boolean onTouch()方法,该方法默认是返回false,表示不消费该事件,后面手指释放若有设置OnClickListener监听器,则会调用OnClickListener的onClick( );如果onTouch()返回了true,则表示消费该事件,那么上面提到的onClick()就得不到执行。
④ 在分发完onTouch()事件之后,会调用View的onTouchEvent()方法。
1.3 附上view的dispatchTouchEvent()部分源码,API 25
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
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;
}
这里省略了一些次要代码:
■ 首先声明了布尔型 result = false 变量,该值是dispathTouchEvent()的返回值;
■ 然后判断mInputEventConsistencyVerifier不为空则调用onTouchEvent()方法;
■ 往下看,根据onFilterTouchEventForSecurity()的返回值来执行里面的代码,这个方法返回该事件是否应该派遣,跟底层有关,一般为true;
■ 看里面的代码,有个ListenerInfo对象li,当li != null , li.mOnTouchListener != null , (mViewFlags & ENABLED_MASK) == ENABLED , li.mOnTouchListener.onTouch(this, event) 都为true时,result = true, 看到li.mOnTouchListener.onTouch(this, event))这个方法,这是就是View的OnTouchListener()监听事件的返回值,当实现该监听方法,并且返回true时,上面那段代码就会将result赋值为true;
■ 最后,返回了result,而dispatchTouchEvent()是在dispatchPointerEvent()里面调用的,返回true则表示当前view直接消费该事件 ,false则会继续分发。
ViewGroup事件分发
再来看看ViewGroup的事件分发:
2.1 ViewGroup事件分发图。
2.2 分发过程。
① 在手机屏幕上发生MotionEvent.DOWN 事件时,即手机在屏幕上按下时,底层将触摸事件传递给顶层的View,即继承的ViewGroup的布局Layout,然后事件也是传递到boolean dispatchTouchEvent(MotionEvent ev)方法,所有的事件都在这个方法里面开始进行分发处理;
② dispatchTouchEvent()有boolean类型的返回值,返回 true 则表示在此view中直接消费掉该事件,可以理解为,直接把事件“吃”了,谁也不给,就自己享用,所以它的子View是不会收到事件的;
③ 在dispatchTouchEvent()的分发过程中,首先会调用boolean onInterceptTouchEvent(MotionEvent ev)方法,根据返回值来确定是否拦截事件,即不往下传,返回true则表示拦截该事件,然后直接调用该view的boolean onTouchEvent(MotionEvent event)方法;这个拦截器onInterceptTouchEvent()经常用于滑动冲突的处理。
④ 若在上面的onInterceptTouchEvent()返回false时,即不拦截事件,那么事件将会分发给下一级子View,在源码中可以看到,调用了child.dispatchTouchEvent( )继续进行往下分发;
⑤ 若子View是ViewGroup,则继续走上面的流程;若子view是View,则走View的分发流程,以此递归实现view的分发机制,实现用户与机器的交互。
2.3 附上ViewGroup的dispatchTouchEvent()部分源码 API 25
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
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);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
for (int i = childrenCount - 1; i >= 0; i--) {
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
break;
}
}
}
}
}
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
}
}
return handled;
}
这里省略了一些次要代码:
■ 首先判断mInputEventConsistencyVerifier不为空则调用onTouchEvent()方法;
■ 然后同样的声明了一个变量handled,并初始化值为false,该值也是最终dispatchTouchEvent()的返回值;
■ 接着判断onFilterTouchEventForSecurity()的返回值,该方法是view的方法:
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
看该方法的注释:Filter the touch event to apply security policies. 即过滤触摸事件的应用安全策略,一般值为true;所以事件将继续往下走;
■ 接下来调用了onInterceptTouchEvent()方法,并用intercepted变量来记录是否拦截的Boolean值;
■ 然后判断if (!canceled && !intercepted) 则继续处理事件,通过for循环遍历该viewGroup的子View,调用dispatchTransformedTouchEvent()来进行分发操作,而该方法是view的一个方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
}
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
可以看到,通过child.dispatchTouchEvent进行分发;
总结
要想随心所欲的自定义View,事件分发就要玩的遛,通过对事件的处理和拦截,可以很轻松的处理滑动冲突等问题,本文如有不足之处,还望多多指教。