只要有点击,就会涉及到事件分发机制,点击屏幕后,view会怎么反应?今天一起来看看事件分发机制的三问:
每个 Activity
包含了一个 Window
对象,这个对象是由 PhoneWindow
做的实现。而 PhoneWindow
将 DecorView
作为了一个应用窗口的根 View,这个 DecorView 又把屏幕划分为了两个区域:一个是 TitleView
,一个是ContentView
,而我们平时在 Xml 文件中写的布局正好是展示在 ContentView 中的。
我觉得事件分发机制流程可以分为三部分,分别是从外传里,从里传外,消费之后
。
1)「首先,从最外面一层传到最里面一层:」
如果当前是viewgroup
层级,就会判断 onInterceptTouchEvent
是否为true,如果为true,则代表事件要消费在这一层级,不再往下传递。接着便执行当前 viewgroup 的onTouchEvent方法。如果onInterceptTouchEvent
为false,则代表事件继续传递到下一层级的 dispatchTouchEvent
方法,接着一样的代码逻辑,一直到最里面一层的view。
伪代码解释:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isConsume = false;
if (isViewGroup) {
if (onInterceptTouchEvent(event)) {
isConsume = onTouchEvent(event);
} else {
isConsume = child.dispatchTouchEvent(event);
}
} else {
//isView
isConsume = onTouchEvent(event);
}
return isConsume;
}
2)「到最里层的view之后,view本身还是可以选择消费或者传到外面。」
到最里面一层就会直接执行onTouchEvent
方法,这时候,view有没有权利拒绝消费事件呢?按道理view作为最底层的,应该是没有发言权才对。但是呢,秉着公平公正原则,view也是可以拒绝的,可以在onTouchEvent
方法返回false,表示他不想消费这个事件。那么它的父容器的onTouchEvent
又会被调用,如果父容器的onTouchEvent又返回false,则又交给上一级。一直到最上层,也就是Activity的onTouchEvent
被调用。
伪代码解释:
public void handleTouchEvent(MotionEvent event) {
if (!onTouchEvent(event)) {
getParent.onTouchEvent(event);
}
}
3)「消费之后」
当某一层viewGroup的onInterceptTouchEvent
为true,则代表当前层级要消费事件。如果它的onTouchListener
被设置了的话,则onTouch会被调用,如果onTouch的返回值返回true,则onTouchEvent
不会被调用。如果返回false或者没有设置onTouchListener,则会继续调用onTouchEvent。而onClick方法则是设置了onClickListener
则会被正常调用。
伪代码解释:
public void consumeEvent(MotionEvent event) {
if (setOnTouchListener) {
int tag = onTouch();
if (!tag) {
onTouchEvent(event);
}
} else {
onTouchEvent(event);
}
if (setOnClickListener) {
onClick();
}
}
解决滑动冲突的根本就是要在适当的位置进行拦截,那么就有两种解决办法:
外部拦截
:从父view端处理,根据情况决定事件是否分发到子view内部拦截
:从子view端处理,根据情况决定是否阻止父view进行拦截,其中的关键就是requestDisallowInterceptTouchEvent
方法。1)「外部拦截法」,其实就是在onInterceptTouchEvnet
方法里面进行判断,是否拦截,见代码:
//外部拦截法:父view.java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
//父view拦截条件
boolean parentCanIntercept;
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (parentCanIntercept) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
return intercepted;
}
还是比较简单的,直接判断拦截条件,然后返回true就代表拦截,false就不拦截,传到子view。注意的是ACTION_DOWN
状态不要拦截,如果拦截,那么后续事件就直接交给父view处理了,也就没有拦截不拦截的问题了。
requestDisallowInterceptTouchEvent
方法让父view不要拦截。 //父view.java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
//子view.java
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
//父view拦截条件
boolean parentCanIntercept;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (parentCanIntercept) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
requestDisallowInterceptTouchEvent(true)
的意思是阻止父view拦截事件,也就是传入true之后,父view就不会再调用onInterceptTouchEvent
。反之,传入false就代表父view可以拦截,也就是会走到父view的onInterceptTouchEvent
方法。所以需要父view拦截的时候,就传入flase,需要父view不拦截的时候就传入true。