注册

高级UI事件分发、事件冲突处理

一、MotionEvent介绍

image.png

二、事件的接收流程。

可根据之前的结成介绍找到入口。

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);
}


0 个评论

要回复文章请先登录注册