高级UI事件分发、事件冲突处理
一、MotionEvent介绍
二、事件的接收流程。
可根据之前的结成介绍找到入口。
viewRootImpl会对事件进行处理,首先找到DecorView,然后再找到activity再在dispatchTouchEvent()里处理。
setView@ViewRootImp.java
--> mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());//接收事件的
-->WindowInputEventReceiver是内部类,事件在onInputEvent(InputEvent event)方法里处理
-->enqueueInputEvent()
-->doProcessInputEvents()
-->deliverInputEvent(q)
-->stage.deliver(q)(InputStage stage;ViewPostImeStage)
-->onprocess()
-->processPointerEvent(q);
//mView是DecorView
-->mView.dispatchPointerEvent(event)//这个方法是View.java
-->dispatchTouchEvent()//这个方法在DecorView.java
-->dispatchTouchEvent@Activity.java
-->getwindow().superDispatchTouchEvent(ev);
superDispatchTouchEvent@PhoneWindow.java
-->mDecor.superDispatchTouchEvent(event)
-->最终调用的是DispatchTouchEvent@ViewGroup.java//我们处理的事件分发机制
-->onTouchEvent()
//事件处理的方法
View.dispatchTouchEvent();
DecoreView.java的dispatchTouchEvent方法。
cb == activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
三、父布局里有一个子view,点击子view的流程打印结果。
1.viewGroup
- dispatchtouchevent()
- onTouchEvent()
- OnInterceptTouchEvent():返回true,子view被拦截
2. view
- onclick()
- onTouch()
- dispatchTouchEvent()
- onTouchEvent()
3. 打印结果
结论:每个事件都会经历父容器到子view
dispatchTouchEvent: 父容器
onInterceptTouchEvent: 父容器
dispatchTouchEvent: 子View
onTouch: 0
onTouchEvent: MotionEvent.ACTION_DOWN = 0
dispatchTouchEvent: 父容器
onInterceptTouchEvent: 父容器
dispatchTouchEvent: 子View
onTouch: 1
MotionEvent.ACTION_UP = 1
onClick
四、事件处理。
1.事件消费(没重写)
当源码中result == true的时候代表这个事件被消费了。
view#disPatchTouchEvent
-->onTouch()//此处执行onTouch方法,则后面的方法不执行。
-->onTouchEvent()//此处是执行onclick()方法的。
-->MotionEvent.Action_up
-->performClick()
-->performClickInternal()
-->onclick() //点击事件执行。
-->MotionEvent.Action_move
-->!pointInView()//当发现移动的时候移出了view,则up的时候就不会触发点击和长按的响应应。
-->MotionEvent.Action_down
-->checkForLongClick()//延时回调,长按的逻辑处理。当在500ms内触发up则取消长按。
2.viewGroup的dispatchTouchEvent的流程
只有第一根手指按下会响应action_down。后续的所有手指都是action_Point_Down.
最后抬起的那根手指是action_up。之前抬起的都是action_point_up
最多识别32跟手指。int有多少位多少根手指。
viewGroup#dispatchTouchEvent
-->1.actionMasked == MotionEvent.ACTION_DOWN//不管是单指还是多指,会进入一次。重置状态
-->2.检测是否拦截。//OnInterceptTouchEvent
-->3.通过条件判断决定是否要分发事件。
-->进入循环判断子view里是否要处理这个事件。
-->4.当子view不处理,自己判断是否处理这个事件。
-->viewgroup进行自己处理事件是调用的view的dispatchTouchEvent()
换个角度去分析
第一块:
- 是否拦截子view
第二块:
- 遍历子view是否处理事件。
第三块:
- 子view不处理,询问自己是否处理。
- 子view处理,分情况。
大概分析一下
在第一块代码,Action_down的时候如果没有拦截子view,则会在第二个块代码遍历找到需要执行事件的view并把target.child记录下来。当后续的action_move就不会走第二块代码,之前记录的target.child去执行move事件。
五、viewgroup嵌套viewGroup事件触发分析
viewpager:横着滑动(左右滑动)。
listview:竖着滑动(上下滑动)。
1.当viewpager的oninterceptTouchEvent返回值为true。
上下不可以滑动,左右可以滑动
2.当viewpager的oninterceptTouchEvent返回值为false。
上下可以滑动,左右不可以滑动。
3.当viewpager的oninterceptTouchEvent返回值为false,当ListView的dispatchTouchEvent返回值为false。
上下不可以滑动,左右能滑动
如何实现上下可以,左右也可以滑动?
两个view叠加在一起,冲突是必然的。
冲突处理:
1.内部拦截法(子view根据条件来让事件由谁触发)要让子view拿到事件。
用此方法,必须能让子view能拿到事件。
子viewgroup
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//让父控件不去拦截自己
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 这个条件由业务逻辑决定,看什么时候 子View将事件让出去
//左右滑动,就让父容器拦截。
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
父viewgroup
// 拦截自己的孩子
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// down事件的时候不能拦截,因为这个时候 requestDisallowInterceptTouchEvent 无效
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
}
return true;
}
2.外部拦截法(由父容器来根据条件让事件由谁来触发)
// 外部拦截法:一般只需要在父容器处理,根据业务需求,返回true或者false
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}