1.1. 一造:直接传给目标Viewbash
咱们先实现一个最简单的需求:Activity 中有一堆层层嵌套的 View,有且只有最里边那个 View 会消费事件 (黄色高亮 View 表明能够消费事件,蓝色 View 表明不消费事件)微信
思考方案:框架
示意图ide
open class MView {
open fun passEvent(ev: MotionEvent) {
// do sth
}
}
class MViewGroup(private val child: MView) : MView() {
override fun passEvent(ev: MotionEvent) {
child.passEvent(ev)
}
}
复制代码
1.2. 二造:从里向外传给目标View布局
而后咱们增长一条需求,让状况复杂一点:Activity中有一堆层层嵌套的View,有好几个叠着的View能处理事件post
同时须要增长一条设计原则:用户的一次操做,只能被一个View真正处理(消费)ui
若是使用第一次试造的框架,要遵照这条原则,就须要在每个能够处理事件的View层级,判断出本身要处理事件后,不继续调用child的passEvent()方法了,保证只有本身处理了事件。 但若是真这样实现了,在大部分场景下会显得怪怪的,由于处理事件的顺序不对:this
因此实现新增需求的一个关键是:找到那个适合处理事件的View,而咱们经过对业务场景进行分析,获得答案是:那个最里面的View适合处理事件spa
这就不能是等parent不处理事件了才把事件传给child,应该反过来,你须要事件的处理顺序是从里向外:里边的child不要事件了,才调用parent的passEvent()方法把事件传出来。 因而得加一条向外的通道,只能在这条向外的通道上处理事件,前面向里的通道什么都不干,只管把事件往里传。 因此这时你有了两条通道,改个名字吧,向里传递事件是passIn()方法,向外传递并处理事件是passOut()方法。设计
示意图
open class MView {
var parent: MView? = null
open fun passIn(ev: MotionEvent) {
passOut(ev)
}
open fun passOut(ev: MotionEvent) {
parent?.passOut(ev)
}
}
class MViewGroup(private val child: MView) : MView() {
init {
child.parent = this // 示意写法
}
override fun passIn(ev: MotionEvent) {
child.passIn(ev)
}
}
复制代码
这段代码没有问题,很是简单,可是它对需求意图的表达不够清晰,增长了框架的使用难度
因而咱们用一个叫作dispatch()的方法单独放事件传递的控制逻辑,用一个叫作onTouch()的方法做为事件处理的钩子,并且钩子有一个返回值,表示钩子中是否处理了事件:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
override fun dispatch(ev: MotionEvent): Boolean {
var handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
return handled
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
这样写完,整个行为其实没有变化,但你会发现:
1.3. 三造:区分事件类型
上文的实现看上去已经初具雏形了,但其实连开始提的那条原则都没实现完,由于原则要求一次操做只能有一个 View 进行处理,而咱们实现的是一个触摸事件只能有一个View进行处理。 这里就涉及到一次触摸操做和一个触摸事件的区别:
因而设计原则更确切地说就是:一次触摸产生的事件流,只能被一个View消费
在上次试造的基础上把一个事件变成一个组事件流,其实很是简单:处理DOWN事件时跟前面处理一个事件时同样,但须要同时记住DOWN事件的消费对象,后续的MOVE/UP事件直接交给它就好了
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) handled = onTouch(ev)
} else {
if (isChildNeedEvent) handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
代码好像增长了不少,其实只多作了两件事:
此时框架使用者仍是只须要关心onTouch()钩子,在须要处理事件时进行处理并返回true,其余事情框架都作好了。
1.4. 四造:增长外部事件拦截
上面的框架已经能完成基本的事件分发工做了,但下面这个需求,你尝试一下用如今框架能实现吗? 需求:在可滑动View中有一个可点击View,须要让用户即便按下的位置是可点击View,再进行滑动时,也能够滑动外面的的可滑动View。
这个需求其实很是常见,好比全部「条目可点击的滑动列表」就是这样的(微信/QQ聊天列表)。
假如使用上面的框架:
因此直接使用如今的模型去实现的「条目可点击的滑动列表」会永远滑动不了。
那怎么办呢?
直接想实现以为处处是矛盾,找不到突破口,那就从头开始吧,从什么样的触摸反馈是用户以为天然的出发,看看这种符合直觉的反馈方案是否存在,找出来它是什么,再考虑咱们要怎么实现:
你先忘记前面说的原则,你想一想,不考虑其余因素,也不是只能用DOWN事件,只要你能判断用户的想法就行,你有什么办法?
看上去这个目标 View 断定方案很不错,安排得明明白白,但咱们现有的事件处理框架实现不了这样的断定方案,至少存在如下两个冲突:
因此要解决上述的冲突,就确定要对上一版的事件处理框架进行修改,并且看上去一不当心就会大改
首先看第二个冲突,解决它的一个直接方案是:调整 dispatch() 方法在传入事件过程当中的人设,让它不是只传递事件了,还能够在往里传递事件前进行拦截,可以看状况拦截下事件并交给本身的 onTouch() 处理
基于这个解决方案,大概有如下两个改动相对小的方案调整思路:
两个思路总结一下:
这两个思路都要对当前框架作改变,看似差很少,但其实仍是有比较明显的优劣的
示意图:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
}
复制代码
写的过程当中增长了一些对细节的处理:
这一下代码是否是看上去瞬间复杂了,但其实只是增长了一个事件拦截机制,对比上一次试造的轮子,会更容易理解。(要是 Markdown 支持代码块内自定义着色就行了)
并且对于框架的使用者来讲,关注点仍是很是少
1.5. 五造:增长内部事件拦截
上面的处理思路虽然实现了需求,但可能会致使一个问题:里边的子 View 接收了一半的事件,可能都已经开始处理并作了一些事情,父 View 突然就不把后续事件给它了,会不会违背用户操做的直觉?甚至出现更奇怪的现象?
这个问题确实比较麻烦,分两类状况讨论
里边的 View 接收了一半事件,但尚未真正开始反馈交互,或者在进行能够被取消的反馈
里边的View接收了一半事件,已经开始反馈交互了,这种反馈最好不要去取消它,或者说取消了会显得很怪
这个时候,事情会复杂一些,并且这个场景发生的远比你想象中的多,形式也多种多样,不处理好的后果也比只是让用户感受上奇怪要严重得多,可能会有的功能会实现不了,下面举两个例子
因此这类问题是必定要解决的,但要怎么解决呢
因此,连同上一次试造,总结一下
另外有几个值得一提的地方:
interface ViewParent {
fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean)
}
open class MView {
var parent: ViewParent? = null
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
open class MViewGroup(private val child: MView) : MView(), ViewParent {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
private var isDisallowIntercept = false
init {
child.parent = this
}
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
// add cancel
val cancel = MotionEvent.obtain(ev)
cancel.action = MotionEvent.ACTION_CANCEL
handled = child.dispatch(cancel)
cancel.recycle()
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
isDisallowIntercept = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
override fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean) {
this.isDisallowIntercept = isDisallowIntercept
parent?.requestDisallowInterceptTouchEvent(isDisallowIntercept)
}
}
复制代码
此次改动主要是增长了发出CANCEL事件和requestDisallowInterceptTouchEvent机制
虽然目前整个框架的代码有点复杂,但对于使用者来讲,依然很是简单,只是在上一版框架的基础上增长了:
到这里,事件分发的主要逻辑已经讲清楚了,不过还差一段 Activity 中的处理,其实它作的事情相似ViewGroup,只有这几个区别:
open class MActivity(private val childGroup: MViewGroup) {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
open fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = childGroup.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
handled = childGroup.dispatch(ev)
}
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
复制代码
因此回头看,你会发现事件分发其实很简单,它的关键不在于「不一样的事件类型、不一样的View种类、不一样的回调方法、方法不一样的返回值」对事件分发是怎么影响的。
关键在于「它要实现什么功能?对实现效果有什么要求?使用了什么解决方案?」,从这个角度,就能清晰并且简单地把事件分发整个流程梳理清楚。
事件分发要实现的功能是:对用户的触摸操做进行反馈,使之符合用户的直觉。
从用户的直觉出发能获得这么两个要求:
用户的一次操做只有一个View去消费
咱们使用了一套简单但有效的先到先得策略,让内外的可消费事件的View拥有近乎平等的竞争消费者的资格:它们都能接收到事件,并在本身断定应该消费事件的时候去发起竞争申请,申请成功后事件就所有由它消费。
模拟View和ViewGroup都不消费事件的场景:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept_BE |type:down
|layer:SViewGroup |on:Intercept_AF |result(super):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch_BE |type:down
|layer:SView |on:Touch_AF |result(super):false |type:down
|layer:SView |on:Dispatch_AF |result(super):false |type:down
|layer:SViewGroup |on:Touch_BE |type:down
|layer:SViewGroup |on:Touch_AF |result(super):false |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:down
|layer:SActivity |on:Touch_BE |type:down
|layer:SActivity |on:Touch_AF |result(super):false |type:down
|layer:SActivity |on:Dispatch_AF |result(super):false |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
...
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SActivity |on:Touch_BE |type:up
|layer:SActivity |on:Touch_AF |result(super):false |type:up
|layer:SActivity |on:Dispatch_AF |result(super):false |type:up
复制代码
这里用BE表明 before,表示该方法开始处理事件的时候,用AF表明after,表示该方法结束处理事件的时候,而且打印处理的结果
从日志中能清楚看到,当View和ViewGroup都不消费DOWN事件时,后续事件将再也不传递给View和ViewGroup
模拟View和ViewGroup都消费事件,同时ViewGroup在第二个MOVE事件时认为本身须要拦截事件的场景:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept |result(false):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch |result(true):true |type:down
|layer:SView |on:Dispatch_AF |result(super):true |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:down
|layer:SActivity |on:Dispatch_AF |result(super):true |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(false):false |type:move
|layer:SView |on:Dispatch_BE |type:move
|layer:SView |on:Touch |result(true):true |type:move
|layer:SView |on:Dispatch_AF |result(super):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(true):true |type:move
|layer:SView |on:Dispatch_BE |type:cancel
|layer:SView |on:Touch_BE |type:cancel
|layer:SView |on:Touch_AF |result(super):false |type:cancel
|layer:SView |on:Dispatch_AF |result(super):false |type:cancel
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Touch |result(true):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Touch |result(true):true |type:up
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:up
|layer:SActivity |on:Dispatch_AF |result(super):true |type:up
复制代码
从日志中能清楚看到,在ViewGroup拦截事件先后,事件是如何分发的
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } 复制代码
定义ListenerInfo局部变量,ListenerInfo是View的静态内部类,用来定义一堆关于View的XXXListener等方法;接着if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))语句就是重点,首先li对象天然不会为null,li.mOnTouchListener经过下面方法赋值
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
复制代码
li.mOnTouchListener是否是null取决于控件(View)是否设置setOnTouchListener监听。接着经过位与运算肯定控件(View)是否是ENABLED 的,默认控件都是ENABLED 的;接着判断onTouch的返回值是否是true。经过如上判断以后若是都为true则设置默认为false的result为true,那么接下来的if (!result && onTouchEvent(event))就不会执行,最终dispatchTouchEvent也会返回true。而若是if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))语句有一个为false则if (!result && onTouchEvent(event))就会执行,若是onTouchEvent(event)返回false则dispatchTouchEvent返回false,不然返回true。
控件触摸就会调运dispatchTouchEvent方法,而在dispatchTouchEvent中先执行的是onTouch方法,因此验证了实例结论总结中的onTouch优先于onClick执行道理。若是控件是ENABLE且在onTouch方法里返回了true则dispatchTouchEvent方法也返回true,不会再继续往下执行;反之,onTouch返回false则会继续向下执行onTouchEvent方法,且dispatchTouchEvent的返回值与onTouchEvent返回值相同。
总结结论
在View的触摸屏传递机制中经过分析dispatchTouchEvent方法源码咱们会得出以下基本结论:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
复制代码
若是一个控件是enable且disclickable则onTouchEvent直接返回false了;反之,若是一个控件是enable且clickable则继续进入过于一个event的switch判断中,而后最终onTouchEvent都返回了true。switch的ACTION_DOWN与ACTION_MOVE都进行了一些必要的设置与置位,接着到手抬起来ACTION_UP时你会发现,首先判断了是否按下过,同时是否是能够获得焦点,而后尝试获取焦点,而后判断若是不是longPressed则经过post在UI Thread中执行一个PerformClick的Runnable,也就是performClick方法。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
复制代码
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
复制代码
控件只要监听了onClick方法则mOnClickListener就不为null,并且有意思的是若是调运setOnClickListener方法设置监听且控件是disclickable的状况下默认会帮设置为clickable。onClick就在onTouchEvent中执行的,并且是在onTouchEvent的ACTION_UP事件中执行的。
总结结论
Android View的触摸屏事件传递机制有以下特征:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
......
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1)处理初始的ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 把ACTION_DOWN做为一个Touch手势的始点,清除以前的手势状态。
cancelAndClearTouchTargets(ev); //清除前一个手势,*关键操做:mFirstTouchTarget重置为null*
resetTouchState(); //重置Touch状态标识
}
// 2)检查是否会被拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 是ACTION_DOWN的事件,或者mFirstTouchTarget不为null(已经找到可以接收touch事件的目标组件)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 判断禁止拦截的FLAG,由于requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法能够禁止执行是否须要拦截的判断
if (!disallowIntercept) {
// 禁止拦截的FLAG为false,说明能够执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法
intercepted = onInterceptTouchEvent(ev); // 此方法默认返回false,若是想修改默认的行为,须要override此方法,修改返回值。
ev.setAction(action);
} else {
// 禁止拦截的FLAG为ture,说明没有必要去执行是否须要拦截了,这个事件是没法拦截的,可以顺利经过,因此设置拦截变量为false
intercepted = false;
}
} else {
// 当不是ACTION_DOWN事件而且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操做。
intercepted = true;
}
// 经过前面的逻辑处理,获得了是否须要进行拦截的变量值
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 不是ACTION_CANCEL而且拦截变量为false
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在ACTION_DOWN时去寻找此次DOWN事件新出现的TouchTarget
final int actionIndex = ev.getActionIndex(); // always 0 for down
.....
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// 根据触摸的坐标寻找可以接收这个事件的子组件
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
// 逆序遍历全部子组件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = i;
final View child = children[childIndex];
// 寻找可接收这个事件而且组件区域内包含点击坐标的子View
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child); // 找到了符合条件的子组件,赋值给newTouchTarget
......
// 把ACTION_DOWN事件传递给子组件进行处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 若是此子ViewGroup消费了这个touch事件
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 则为mFirstTouchTarget赋值为newTouchTarget,此子组件成为新的touch事件的起点
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
......
}
}
// 通过前面的ACTION_DOWN的处理,有两种状况。
if (mFirstTouchTarget == null) {
// 状况1:(mFirstTouchTarget为null) 没有找到可以消费touch事件的子组件或者是touch事件被拦截了,
// 那么在ViewGroup的dispatchTransformedTouchEvent方法里面,处理Touch事件则和普通View同样,
// 本身没法消费,调用super.dispatchOnTouchEvent()把事件回递给父ViewGroup进行处理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 状况2:(mFirstTouchTarget!=null) 找到了可以消费touch事件的子组件,那么后续的touch事件均可以传递到子View
TouchTarget target = mFirstTouchTarget;
// (这里为了理解简单,省略了一个Target List的概念,有须要的同窗再查看源码)
while (target != null) {
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 若是前面利用ACTION_DOWN事件寻找符合接收条件的子组件的同时消费掉了ACTION_DOWN事件,这里直接返回true
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
// 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理(注意这里的非ACTION_DOWN事件已经不须要再判断是否拦截)
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
// 若是target子组件进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件
// 条件是:若是ACTION_DOWN时没有被拦截,然后面的touch事件被拦截,则须要发送ACTION_CANCEL给target子组件
handled = true;
}
......
}
}
}
if (canceled || actionMasked == MotionEvent.ACTION_UP) {
// 若是是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都没法派发给子View
resetTouchState();
}
......
return handled;
}
复制代码
ACTION_DOWN时进行一些初始化操做,清除以往的Touch状态而后开始新的手势。在这里你会发现cancelAndClearTouchTargets(ev)方法中有一个很是重要的操做就是将mFirstTouchTarget设置为了null,接着在resetTouchState()方法中重置Touch状态标识。
使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递,该变量相似第一步的mFirstTouchTarget变量,在后续代码中起着很重要的做用。if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)这一条判断语句说明当事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到可以接收touch事件的目标组件)时if成立,不然if不成立,而后将intercepted设置为true,也即拦截事件。当事件为ACTION_DOWN或者mFirstTouchTarget不为null时判断disallowIntercept(禁止拦截)标志位,而这个标记在ViewGroup中提供了public的设置方法
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
复制代码
在其余地方调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,从而禁止执行是否须要拦截的判断。当disallowIntercept为true(禁止拦截判断)时则intercepted直接设置为false,不然调用onInterceptTouchEvent(ev)方法,而后将结果赋值给intercepted。那就来看下ViewGroup与View特有的onInterceptTouchEvent方法,以下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
复制代码
经过标记和action检查cancel,而后将结果赋值给局部boolean变量canceled。
获取一个boolean变量标记split来标记,默认是true,做用是是否把事件分发给多个子View,这个一样在ViewGroup中提供了public的方法设置,以下:
public void setMotionEventSplittingEnabled(boolean split) {
if (split) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
} else {
mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
}
}
复制代码
if (!canceled && !intercepted)判断代表,事件不是ACTION_CANCEL而且ViewGroup的拦截标志位intercepted为false(不拦截)则会进入其中。
if语句if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)处理ACTION_DOWN事件,判断了childrenCount个数是否不为0,而后接着拿到了子View的list集合preorderedList;接着经过一个for循环i从childrenCount - 1开始遍历到0,倒序遍历全部的子view,这是由于preorderedList中的顺序是按照addView或者XML布局文件中的顺序来的,后addView添加的子View,会由于Android的UI后刷新机制显示在上层;假如点击的地方有两个子View都包含的点击的坐标,那么后被添加到布局中的那个子view会先响应事件;这样其实也是符合人的思惟方式的,由于后被添加的子view会浮在上层,因此咱们去点击的时候通常都会但愿点击最上层的那个组件先去响应事件。
经过getTouchTarget去查找当前子View是否在mFirstTouchTarget.next这条target链中的某一个targe中,若是在则返回这个target,不然返回null。在这段代码的if判断经过说明找到了接收Touch事件的子View,即newTouchTarget,那么,既然已经找到了,因此执行break跳出for循环。若是没有break则继续向下执行,这里你能够看见一段if判断的代码if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),调用方法dispatchTransformedTouchEvent()将Touch事件传递给特定的子View。该方法十分重要,在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法。在dispatchTouchEvent()中若是子View为ViewGroup而且Touch没有被拦截那么递归调用dispatchTouchEvent(),若是子View为View那么就会调用其onTouchEvent()。dispatchTransformedTouchEvent方法若是返回true则表示子View消费掉该事件,同时进入该if判断。知足if语句后重要的操做有:
若是if判断中的dispatchTransformedTouchEvent()方法返回false,即子View的onTouchEvent返回false(即Touch事件未被消费),那么就不知足该if条件,也就没法执行addTouchTarget(),从而致使mFirstTouchTarget为null(无法对mFirstTouchTarget赋值,由于上面分析了mFirstTouchTarget一进来是ACTION_DOWN就置位为null了),那么该子View就没法继续处理ACTION_MOVE事件和ACTION_UP事件。
若是if判断中的dispatchTransformedTouchEvent()方法返回true,即子View的onTouchEvent返回true(即Touch事件被消费),那么就知足该if条件,从而mFirstTouchTarget不为null。
if (newTouchTarget == null && mFirstTouchTarget != null)。该if表示通过前面的for循环没有找到子View接收Touch事件而且以前的mFirstTouchTarget不为空则为真,而后newTouchTarget指向了最初的TouchTarget。
对于此处ACTION_DOWN的处理具体体如今dispatchTransformedTouchEvent()方法,该方法返回值具有以下特征:
由于在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent(),因此dispatchTransformedTouchEvent()的返回值其实是由onTouchEvent()决定的。简单地说onTouchEvent()是否消费了Touch事件的返回值决定了dispatchTransformedTouchEvent()的返回值,从而决定mFirstTouchTarget是否为null,进一步决定了ViewGroup是否处理Touch事件,通过上面对于ACTION_DOWN的处理后mFirstTouchTarget可能为null或者不为null。
mFirstTouchTarget为null时,也就是说Touch事件未被消费,即没有找到可以消费touch事件的子组件或Touch事件被拦截了,则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件(和普通View同样),即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件。具体就是在调用dispatchTransformedTouchEvent()时第三个参数为null .子view对于Touch事件处理返回true那么其上层的ViewGroup就没法处理Touch事件了,子view对于Touch事件处理返回false那么其上层的ViewGroup才能够处理Touch事件。mFirstTouchTarget不为null时,也就是说找到了能够消费Touch事件的子View且后续Touch事件能够传递到该子View。能够看见在源码的else中对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()方法来实现的处理。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
复制代码
若是ViewGroup的onInterceptTouchEvent返回false就不阻止事件继续传递派发,不然阻止传递派发。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
复制代码
在dispatchTouchEvent()中调用dispatchTransformedTouchEvent()将事件分发给子View处理。在此咱们须要重点分析该方法的第三个参数(View child)。在dispatchTouchEvent()中屡次调用了dispatchTransformedTouchEvent()方法,并且有时候第三个参数为null,有时又不是,他们到底有啥区别呢?这段源码中很明显展现告终果。在dispatchTransformedTouchEvent()源码中能够发现屡次对于child是否为null的判断,而且均作出以下相似的操做。其中,当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理,即super.dispatchTouchEvent(event)(也就是View的这个方法,由于ViewGroup的父类是View);当child != null时会调用该子view(固然该view多是一个View也多是一个ViewGroup)的dispatchTouchEvent(event)处理,即child.dispatchTouchEvent(event)。 ViewGroup没有重写View的onTouchEvent(MotionEvent event) 方法.
总结结论
Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。 在ViewGroup中能够经过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true表明不容许事件继续向子View传递,返回false表明不对事件进行拦截,默认返回false。 子View中若是将传递的事件消费掉,ViewGroup中将没法接收到任何事件。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
Activity的attach方法能够发现getWindow()返回的就是PhoneWindow对象(PhoneWindow为抽象Window的实现子类),那就简单了,也就至关于PhoneWindow类的方法,而PhoneWindow类实现于Window抽象类,因此先看下Window类中抽象方法的定义,以下:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
复制代码
PhoneWindow里看下Window抽象方法的实现吧,以下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
在PhoneWindow类里发现,mDecor是DecorView类的实例,同时DecorView是PhoneWindow的内部类。最惊人的发现是DecorView extends FrameLayout implements RootViewSurfaceTaker,看见没有?它是一个真正Activity的root view,它继承了FrameLayout。
DecorView类的superDispatchTouchEvent方法吧,以下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
复制代码
Activity的dispatchTouchEvent方法的if (getWindow().superDispatchTouchEvent(ev))本质执行的是一个ViewGroup的dispatchTouchEvent方法(这个ViewGroup是Activity特有的root view,也就是id为content的FrameLayout布局).
在Activity的触摸屏事件派发中:
public void onUserInteraction() {
}
复制代码
此方法是activity的方法,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法。下拉statubar、旋转屏幕、锁屏不会触发此方法。因此它会用在屏保应用上,由于当你触屏机器 就会立马触发一个事件,而这个事件又不太明确是什么,正好屏保知足此需求;或者对于一个Activity,控制多长时间没有用户点响应的时候,本身消失等。
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
复制代码
若是一个屏幕触摸事件没有被这个Activity下的任何View所处理,Activity的onTouchEvent将会调用。这对于处理window边界以外的Touch事件很是有用,由于一般是没有View会接收到它们的。返回值为true代表你已经消费了这个事件,false则表示没有消费,默认实现中返回false。
继续分析吧,重点就一句,mWindow.shouldCloseOnTouch(this, event)中的mWindow实际就是上面分析dispatchTouchEvent方法里的getWindow()对象,因此直接到Window抽象类和PhoneWindow子类查看吧,发现PhoneWindow没有重写Window的shouldCloseOnTouch方法,因此看下Window类的shouldCloseOnTouch实现吧,以下:
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
复制代码
判断mCloseOnTouchOutside标记及是否为ACTION_DOWN事件,同时判断event的x、y坐标是否是超出Bounds,而后检查FrameLayout的content的id的DecorView是否为空。其实没啥过重要的,这只是对于处理window边界以外的Touch事件有判断价值而已。