书接上文:事件分发浅析 - 仿京东首页二级联动
结构
事件
事件就是MotionEvent对象。
ACTION_DOWN:事件开始,主要处理事件的分发,分发完了会给mFirstTouchTarget赋值(处理事件的子View链表)
ACTION_MOVE:move事件会多次触发,对于View来说,没有down,一定不会有move。但是ViewGroup,没有down也会有第二次move以后的事件。因为是多次触发的,所以看源码得多次看
ACTION_UP:事件结束
ACTION_CANCEL:在Move事件的时候,会赋值成cancel,第二次move以后可以被上层拦截
View与ViewGroup结构
1.ViewGroup extends View。
所以在ViewGroup没有实现的方法会调用View方法。ViewGroup是没有实现事件处理的,所以都会调用View的事件处理dispatchTouchEvent
2.从布局角度,View的父亲一定是ViewGroup。
所以ViewGroup一层一层的下发事件,给View。ViewGroup中可能会将事件发给自己的子View,或者调用自己的(extends View)的dispatchTouchEvent来处理。
事件处理: View.java --> dispatchTouchEvent
事件分发: ViewGroup.java --> dispatchTouchEvent
事件冲突
事件只有一个,但是有多个人想处理,如果处理的对象不是我们想给的对象,那么就是说发生了事件冲突。
解决:
解决事件冲突有两种:内部拦截和外部拦截。
子View去处理,叫内部拦截。他爹ViewGroup去处理,叫外部拦截。
整体流程
事件处理 - View
先结论,事件处理就View的onClick和onTouch。
onTouch先执行,onClick后执行。
当onTouch返回false的时候,onClick会执行。
当onTouch返回true的时候,onClick不执行。表示事件onTouch消费了。onClick没有返回值是因为,执行onClick的时候事件就已经消费了。
源码
View.java 的dispatchTouchEvent()
往下看
核心代码
事件处理的核心代码就这么一点
1.Java中的 && 处理的逻辑是这样的,if(a && b && c) 当a=false,直接不执行b。b=false,不执行c
ListenerInfo li = mListenerInfo;这个是啥呢?
点进getListenerInfo()
所以前面几个条件中的mListenerInfo就是setOnTouchListener中new的OnTouchListener,也就是只要实现了,setOnTouchListener那么前几个条件都会成立,所以必会执行li.mOnTouchListener.onTouch(this, event))
点进li.mOnTouchListener.onTouch(this, event))发现是个接口,
也就是会执行到自定义的onTouch
自定义的onTouch里的返回值true,false就会影响到View.dispatchTouchEvent()的返回值。所以onTouch里返回true就代表事件被消费了,false=没有消费
所以自定义的onTouch里的返回值true,
result = true;
if (!result && onTouchEvent(event))第一个条件不成立,就不会执行onTouchEvent(event)
也就是不会执行onClick()在onTouchEvent(event) ACTION_UP方法里
onTouchEvent(event)
onTouchEvent()里有down/up等各种事件的处理。其中onClick在Up事件里。
onTouchEvent的up事件里有一个performClick
点进去
performClick()方法
1.处理按键声音
2.处理onClick
3.result = true;
这样事件的处理就完事了
事件分发 - ViewGroup
事件分发是在ViewGroup中的dispatchTouchEvent(),这里的源码是重点。
精简源码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...省略
if (onFilterTouchEventForSecurity(ev)) {
//...省略
if (actionMasked == MotionEvent.ACTION_DOWN) {
/**清空上次事件缓存*/
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/**拦截判断*/
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 {
intercepted = true;
}
//...省略
/**事件分发*/
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//...省略
//子View个数
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
/**遍历子View*/
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
//...省略
/**判断子View是否需要分发*/
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...省略
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
/**具体执行分发*/
//是否拦截
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
return handled;
}
事件分发结构 -- Down事件!
1.清空缓存
跳过一些辅助功能等代码
2.拦截判断
1.如果是ACTION_DOWN 或 mFirstTouchTarget != null(刚开始一定是null的,没有走遍历子View之前)
2.子View没有设置不可拦截(尚方宝剑 requestDisallowInterceptTouchEvent())
3.一般都走这onInterceptTouchEvent(),重点方法
所以自己处理是否拦截,需要重写onInterceptTouchEvent()方法,返回true表示拦截
3.拦截判断 - 拦截时(onInterceptTouchEvent()返回true)
中间大段代码直接跳过。 直接走最后的分发执行流程
dispatchTransformedTouchEvent()这个方法是事件分发的处理方法,重点!!!,拦截时child为null
点进去:
当child为空时,调用的 handled = super.dispatchTouchEvent(transformedEvent);
也就是调用的自己的(Viewgroup)extends(继承的) View中的dispatchTouchEvent
返回值:handled就是是否处理完事
4.拦截判断 - 不拦截时 (onInterceptTouchEvent()返回false)
- 不拦截时,会走
if (!canceled && !intercepted)这
- down事件的正常流程
1.先判断是否是down事件
2.清空指针
3.判断子View是否有,有的话,根据xml顺序倒叙插入到一个ArrayListArrayList(xml里是textView/ImagView... 那么存的时候List(0) =ImagView,list(1) = textView )
4.for循环从后开始取,这样取出来又是根据xml的顺序排的了
- for遍历子View,判断是否分发
重点!!! if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))这个方法来判断儿子是否消费,不消费就for下一个,消费那就addTouchTarget添加到mFirstTouchTarget然后break跳出循环。
3.1 点进dispatchTransformedTouchEvent方法
点进dispatchTransformedTouchEvent方法,当child不为空时,会调用
handled = child.dispatchTouchEvent(transformedEvent);
也就是当child为view时,会调用View的dispatchTouchEvent去处理事件,当child为ViewGroup时,会调用ViewGroup的dispatchTouchEvent去(递归)分发事件,再去遍历儿子的儿子...一层层下去。
- 确定分发
这里重点三(第7步用):
target.next = null
mFirstTouchTarget = target; !=null
alreadyDispatchedToNewTouchTarget = true
- 如果一顿for遍历下来,没有子类处理这个事件,也就是所有子View的
dispatchTransformedTouchEvent()都为false,那么走之前拦截的代码
- 如果有要处理的子View,addTouchTarget以后break;跳出for循环,走mFirstTouchTarget!= null 去分发事件
- 分发事件里是
while (target != null)这个while循环一般只会走一次(单点触摸的时候,多点触摸走多次)。
第五步:for循环找子View处理的时候,其实已经事件分发给View完事了,所以这里直接标记handled =true返回
事件分发 -- Move事件!
down事件已经确定了具体哪个View来消费事件,但是Move事件还是会总顶层ViewGroup开始走
1.清缓存这步不走
- 因为mFirstTouchTarget != null 所以还是会走是否拦截的流程
- (如果本来就没拦截) 那么if (!canceled && !intercepted) 这里依然会走。down的时候分发事件完事了,所以不会走子View分发事件了
- 跳过分发事件代码以后,就到最后一步,因为mFirstTouchTarget!=null ,所以会走else
- dispatchTransformedTouchEvent()分发事件,分发给之前存下来的子View
事件冲突解决
事件冲突只能在Move时候处理!!!
事件冲突的解决方法分为内部拦截(子View处理)和外部拦截(父容器处理)。
内部拦截
内部拦截,子View中的dispatchTouchEvent通过 getParent().requestDisallowInterceptTouchEvent来设置父亲是否有权拦截
getParent().requestDisallowInterceptTouchEvent();这个方法就是上面代码的尚方宝剑。子View可以设置父容器不让他拦截。
但是这样写完还不够,因为ViewGroup DOWN的时候会清空缓存,所以子View怎么设置都不行,所以还得处理父亲的Down
外部拦截
外部拦截是通过设置父容器的onInterceptTouchEvent 来控制,父容器是否拦截