事件MotionEvent包含了哪几个?
ACTION_DOWN
手指触碰到屏幕时触发,只会执行一次ACTION_MOVE
手指在屏幕上滑动出发,会执行多次ACTION_UP
手指抬起离开屏幕出发,只会执行一次ACTION_CANCEL
事件被上层拦截时会触发- 父容器
ViewGroup
需要从子View
手中抢夺分发的事件进行处理时,会用到ACTION_CANCEL
ViewGroup
的事件分发中,通过如下代码会将事件状态置为ACTION_CANCEL
:
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- 所以,在父容器
ViewGroup
在进行事件分发时,父容器ViewGroup
需要将事件从子View
中抢夺过来;- 处理第一次
ACTION_MOVE
事件中,cancelChild
值为true
时,会通过dispatchTransformedTouchEvent()
方法中,将状态更改为ACTION_CANCEL
,这个操作会执行一次子View
的事件分发dispatchTouchEvent
方法,并且状态是ACTION_CANCEL
,也就是说子,这会将子View
没有消费该事件View
中的事件剥夺给到父容器ViewGroup
; - 在父容器中进行第二次
ACTION_MOVE
的操作时,父容器ViewGroup
进行事件处理
- 处理第一次
- 父容器
总结:
View
是负责事件处理onTouchEvent
的,而ViewGroup
主要是负责事件分发的dispatchTouchEvent()
.
事件的接收流程?(事件是在哪里接收的?)
- 通过
WindowInputEventReceiver
接收事件
- 在
ViewRootImpl.java
的setView()
方法中,通过如下对象WindowInputEventReceiver
去接收我们的事件,代码如下:
mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
- 接收到的事件最后进入到
Activity
的dispatchTouchEvent()
去处理,过程如下:
- 接着我们去研究
WindowInputEventReceiver
中的onInputEvent
方法, - 然后调用
enqueueInputEvent
方法, - 接着调用
doProcessInputEvents
方法, - 然后调用
deliverInputEvent(q)方法
, - 接着调用
stage.deliver(q)
方法, - 接着调用
onProcess(q)
方法,注意这里stage
实际调用的是实现类ViewPostImeInputStage
的onProcess
方法, - 接着调用
processPointerEvent(q)
方法, - 然后调用
mView.dispatchPointerEvent(event)
,注意这里的mView
是DecorView
,在DecorView
的终极超类View
中才有重写dispatchPointerEvent(event)
方法; - 接着调用
View.java
中的dispatchTouchEvent
方法,这里会调用到实现类DecorView
中的dispatchTouchEvent
方法, - 这里会通过
cb.dispatchTouchEvent(ev)
调用到Activity
中的dispatchTouchEvent()
方法- 解释说明
Activity
由来://这里的变量cb其实是Activity, //因为在(PhoneWindow)Window中setCallback(callback)方法, //传入的参数值是Activity的this引用 Window.Callback cb = mWindow.getCallback();
- 解释说明
- 接着从
Activity
中的dispatchTouchEvent()
方法往下分析; - 接着执行
PhoneWindow
中的superDispatchTouchEvent()
方法; - 接着执行
DecorView
中的superDispatchTouchEvent
方法; - 接着通过
super.dispatchTouchEvent(event)
执行到了ViewGroup
中的dispatchTouchEvent()
方法 - 最后将事件分发给
ViewGroup
的dispatchTouchEvent()
方法 - 然后分发给
View
的dispatchTouchEvent()
方法,最后交由View
的onTouchEvent()
方法进行事件处理
总结:
ViewRootImpl
中WindowInputEventReceiver
接收事件,依次交给Activity
、PhoneWindow
、DecorView
、ViewGroup
的dispatchTouchGroup()
方法将事件分发下去,最终会由View
的onTouchEvent()
方法对我们的事件进行处理.
事件分发机制流程图如下:
事件处理的几个方法
dispatchTouchEvent
分发事件onInterceptTouchEvent
拦截事件onTouchEvent
处理事件
View的dispatchTouchEvent()方法分析
这个方法中包含如下代码逻辑:
//这里的li和li.mOnTouchListener是在view.setOnTouchListener函数中赋值的,所以这两个逻辑判断都成立;
//同时(mViewFlags & ENABLED_MASK) == ENABLED也成立,所以就会调用onTouch方法,也就是调用我们页面中的OnTouchListener的回掉方法onTouch(),如果该方法返回true,这个if判断就成立,result的值为true;若不成立,result的值仍为false
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//若result返回值为false,就会执行onTouchEvent方法,该方法中会执行onClick方法,所以如果onTouch方法返回值为true,那么就不会执行onTouchEvent方法中的onClick方法
//短路与&&,不执行后面的逻辑判断
if (!result && onTouchEvent(event)) {
result = true;
}
- 假如在
Activity
中调用view.setOnTouchListener
,同时回调onTouch
方法,该方法返回boolean
类型的值; - 若
onTouch
方法返回true
,则上面代码中result
值为true
;若onTouch
方法返回false
,result
值为false
; result
值为false
,进入第二个if
逻辑判断时会调用onTouchEvent
方法中的onClick
方法;result
值为true
,则不会调用onTouchEvent
方法中的onClick
方法;
分析onTouchEvent中的到onClick方法的经历了哪些过程
- 通过日志,发现在
onToucheEvent
中的MotionEvent.ACTION_UP
的时候执行了onClick
方法; - 接着执行了
mPerformClick = new PerformClick();
- 接着执行了
performClickInternal()
方法; - 接着执行了
performClick()
方法; - 这个方法就会去执行我们的
li.mOnClickListener.onClick(this)
方法,也就是onClick()
方法;
总结:
在View
的onToucheEvent()
中的MotionEvent.ACTION_UP
逻辑中执行了onClick()
方法,执行了onClick()
方法就表示该事件被消费了!!!
执行了onClick()
方法就表示该事件被消费了!!!
执行了onClick()
方法就表示该事件被消费了!!!
分析:
- onTouch和onClick的关系,执行的位置?
onTouch
和onClick
是冲突关系;onTouch
方法返回true
,就不会执行onClick
方法;onTouch
方法返回false
,执行完onTouch
方法后,还会继续执行onClick
方法.
- onTouchEvent在哪里执行的?
onTouchEvent
在dispathTouchEvent
方法中执行,执行条件是在设置setOnTouchListener
监听事件,根据onTouch
方法返回值为false
,dispathTouchEvent
方法中短路与不成立,才会执行onTouchEvent()
方法,否则只会执行onTouch
事件.
- onLongClick
- 在
onTouchEvent
中的MotionEvent.ACTION_DOWN
逻辑判断中,会执行包含有onLongClick
方法的逻辑代码,如果长按时间过短,在MotionEvent.ACTION_UP
中会移除长按事件,执行代码:
removeLongPressCallback();
- 在
- 按下view不松手,手指移到view外面,为什么不执行onClick方法?
- 因为将手指移出
View
外面,也就是在执行onTouchEvent
中的MotionEvent.ACTION_MOVE
逻辑判断中,有一个中心点移出View
外的判断;if (!pointInView(x, y, touchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { //这个方法会将执行mPrivateFlags &= ~PFLAG_PRESSED //这就导致在MotionEvent.ACTION_UP逻辑判断中不会走onClick的逻辑判断 setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; }
- 中心点移出
View
外面,会导致在MotionEvent.ACTION_UP
逻辑中,包含有onClick
的逻辑判断不成立,所以就不会执行onClick
方法.
- 因为将手指移出
ViewGroup的dispatchTouchEvent事件分发流程?
第一块拦截:
(判断是拦截,并生成变量intercepted
;值true
会拦截第二块代码,值为false
不会拦截第二块代码)- 可以通过
requestDisallowInterceptTouchEvent(boolean disallowIntercept)
方法更改事件分发中disallowIntercept
变量的值, - 这个值
disallowIntercept
决定了是否执行事件拦截方法onInterceptTouchEvent()
- 然后将事件拦截方法
onInterceptTouchEvent()
的返回值赋值给一个变量intercepted
- 可以通过
第二块拦截:
(遍历子View
,询问子View
是否处理事件)- 判断第一块代码拦截到的变量
intercepted
和另外一个变量组成的逻辑判断是否成立,if (!canceled && !intercepted){...}
,成立则进入该if
判断 - 将子
View
添加到集合中,并且从集合中倒序取出子View
;类似于FrameLayout层级结构,布局文件中最后的的布局文件展示在屏幕的最顶层 - 遍历所有子
View
,判断手指触摸点是否在该子View
身上,在该子View
身上再询问子View
是否消费该事件 - 通过
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
方法来判断,事件是否被该子View
消费掉了,该方法返回true
表示子View
消费了该事件,最终返回一个变量标记事件消费过了:
alreadyDispatchedToNewTouchTarget = true
; - 同时这个方法中会判断,是由
ViewGroup
还是由他的父类View
来处理事件分发,代码如下:if (child == null) { //ViewGroup的super父类是View,所以这里调用View的事件分发 //会判断事件是否在View的事件分发中进行处理 handled = super.dispatchTouchEvent(event); } else { //这里调用ViewGroup的事件分发 handled = child.dispatchTouchEvent(event); }
- 判断第一块代码拦截到的变量
第三块代码:
(所有的子View
都没有消费该事件,那么询问当前ViewGroup
是否处理该事件)- 通过这个方法来询问当前
ViewGroup
是否处理事件,dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)
,该方法返回true
,表示ViewGroup
消费了该事件;否则,表示事件最终都没有被消费,最后ViewGroup
的dispatchTouchEvent()
方法返回false
; - 注意:这里第三个参数传的
null
,在询问子View
是否处理该事件的时候,第三个参数传的子Viewchild
.
- 通过这个方法来询问当前
总结:
ViewGroup
的dispatchTouchEvent()
方法中有3
块拦截代码:
- 第一块代码判断事件是否允许被拦截,允许被拦截才会执行
onInterceptTouchEvent(ev)
方法;- 第二块代码判断父容器
ViewGroup
中的子View
是否会消费该事件;前提是父容器ViewGroup
不拦截,也就是onInterceptTouchEvent(ev)
方法返回false
;- 第三块代码判断当前父容器
ViewGroup
是否消费该事件.结论:
如果子View
和当前父容器ViewGroup
都没有消费该事件,就会把该事件依次分发给DecorView
、PhoneWindow
、Activity
来处理,如果事件还没有被处理,那么该事件就不会被处理.
View的事件分发dispatchTouchEvent方法分析
- 根据
OnTouchListener
中onTouch()
方法的返回值,会判断是否执行View
的onTouchEvent()
方法; - 根据在
View
的onTouchEvent()
方法中会判断是否执行onClick()
和onLongClick()
方法; - 如果
View
的dispatchTouchEvent()
方法返回false
,表示View
中不会对事件进行处理.
总结:
- 父容器
ViewGroup
负责事件分发,最终分发给ViewGroup
的super.dispatchTouchEvent()
方法,ViewGroup
的super
父类是View
,也就是执行View
中的事件分发dispatchTouchEvent()
方法;- 这个
View
的dispatchTouchEvent()
方法中会执行onTouch()
方法;
- 若
onTouch()
方法返回true
,表示事件被消费掉了;- 若
onTouch()
方法返回false
,同onTouchEvent()
方法一起进行短路与&&
运算,只有onTouch()
方法返回false
才会执行onTouchEvent()
方法,onTouchEvent()
方法返回true
表示事件被消费掉了.View
的事件分发dispatchTouchEvent()
方法返回true
表示事件被消费掉了;否则,表示事件没有被消费.
单指操作和多指操作
- 单指操作:
- MotionEvent_ACTION_DOWN 只会执行一次
- MotionEvent_ACTION_MOVE
- …
- MotionEvent_ACTION_MOVE
- MotionEvent_ACTION_UP 只会执行一次
- 多指操作:
- MotionEvent_ACTION_DOWN 只会执行一次 第1根手指按下
- MotionEvent_ACTION_POINTER_DOWN 这里是第2根手指按下
- MotionEvent_ACTION_POINTER_DOWN 这里是第3根手指按下
- MotionEvent_ACTION_MOVE
- …
- MotionEvent_ACTION_MOVE
- MotionEvent_ACTION_POINTER_UP 倒数第3根手指抬起
- MotionEvent_ACTION_POINTER_UP 倒数第2根手指抬起
- MotionEvent_ACTION_UP 只会执行一次 最后1根手指抬起
处理View嵌套冲突的方法?
- 内部处理法
- 在子
View
中,根据条件来判断事件由子View
处理,还是父容器ViewGroup
处理 - 主要是通过这个方法来处理:
getParent().requestDisallowInterceptTouchEvent(true);
- 在子
- 外部处理法
- 在父容器
ViewGroup
中,判断事件由子View
处理,还是由父容器ViewGroup
处理.
- 在父容器