Android 事件分发之源码分析系列
Android 之事件分发基础篇 [ 一 ]
Android 之 ViewGroup 事件分发深入源码分析 [ 二 ]
Android 之 View 事件分发深入源码分析 [ 三 ]
Android 之 View 事件分发深入源码分析 [ 总结 ]
上一篇文章学习了 ViewGroup
的事件分发, 对于 ViewGroup
中的事件分发来说最重要的就是 dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent
这三个方法了.
View
和Activity
拥有dispatchTouchEvent, onTouchEvent
两个方法.
并且在上一篇文章的第五点 dispatchTransformedTouchEvent
中, 最后都调用了 super.dispatchTouchEvent(event);
接下来一起看一下, 事件到 View
中的执行过程吧.
虽然
ViewGroup
是View
的之类, 但是这里所说的View
, 专指除ViewGroup
外的View
控件, 例如TextView, Button, CheckBox
等控件本身已经是最小的单位, 不能再作为其他View
的容器.
这次依然开始先抛出几个问题. 我们将在源代码中寻找答案.
- 在什么时候会调用 onTouch 方法.
- onTouch 和 onTouchEvent 谁会先执行.
- onClick 方法会在什么时候被调用.
- 默认情况下, 控件都是 enable 的, 我们重写了 onClick, onTouch, 但如果设置了 setEnable(false), 会怎么样
1. View.dispatchTouchEvent
View.java 12478行
public boolean dispatchTouchEvent(MotionEvent event) {
//通过触摸事件判断是否应该被有焦点的 View 处理事件, 如果同时存在拥有焦点的 View, 则设置为 False
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
//设置默认的返回值
boolean result = false;
//输入一致性校验, 在上一篇 4.1 中的分析 2 有说明
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//获取事件类型, 在上一篇 4.1 中的分析 6-7 中有说明.
final int actionMasked = event.getActionMasked();
//如果当前事件类型为 按下事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 停止正在进行的滑动或者依赖滑动
stopNestedScroll();
}
//是否要过滤掉本次触摸事件. 若窗口被遮挡,返回 false.
if (onFilterTouchEventForSecurity(event)) {
//如果当前 view 没有被禁用, 并且没有将事件作为滚动事件.
//handleScrollBarDragging. 如果将时间作为滚动处理, 返回 true, 否则为 false. 这里会返回 false.
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//那么就来到了这一步.
//ListenerInfo 是view的一个内部类, 里面有各种各样的listener,
//例如OnClickListener,OnLongClickListener,OnTouchListener等等
ListenerInfo li = mListenerInfo;
//条件 1: li 对象是否为 null.
//条件 2: 判断是否通过 setOnTouchListener 设置了监听,即是否有实现 OnTouchListener,
//条件 3: 判断当前 view 的状态是不是启用的,
//条件 4: 判断实现的 OnTouchListener 中我们重写的 onTouch 是否返回 true,(在这里调用了 onTouch 方法, 返回 true, 表示处理了事件),
//上面 4 个条件全部为true,则整个方法直接返回 true.
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果上一判断不成立,才会去调用 onTouchEvent 方法.
//这里证明了, onTouchEvent() 方法调用优先级低于 onTouch() 方法.
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
问题1 和问题 2, 都在 View.dispatchTouchEvent
得到了答案.
- 在什么时候会调用 onTouch 方法.
在
View
的dispatchTouchEvent
中判断的时候, 先判断ListenerInfo
对象是否为null
, 再判断是否实现了OnTouchListener
, 接着判断View
是否停用. 最后才来调用我们重写的onTouch
方法.
- onTouch 和 onTouchEvent 谁会先执行.
在调用
onTouch
方法的那个判断条件中, 如果判断不成立, 才会调用onTouchEvent
方法, 也就是说,onTouch
方法的优先级高, 如果在调用onTouch
方法中, 返回了true
, 也就是处理了事件, 那么就不会有onTouchEvent
的什么事了. 不会再调用onTouchEvent
.
那么接下来是 View.onTouchEvent 的分析.
2. View.onTouchEvent
View.java 13718行.
public boolean onTouchEvent(MotionEvent event) {
...
//判断是否是可点击或者长按,CLICKABLE 有的控件默认是 true, 有的默认是 false.
//控件的 flag 一般为点击和长按, 都表示为可点击.
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果控件为 disabled, 即调用了 setEnable(false), 这里直接就返回出去了.
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//可点击的禁用视图会消耗触摸事件, 只是不会响应.
return clickable;
}
//如果设置的有委托, 则先调用委托的 onTouchEvent, 如果委托的 onTouchEvent 返回 true, 则表示消耗了事件.
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//重点.
//分析 1
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
// 分析 2
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
分析 1
如果当前控件是可点击的,或者可以悬停和长按显示工具提示.就会进入if
内部逻辑判断, 在逻辑内一定是会return true
的. 代表消费掉本次事件., 否则直接return false
不消费本次事件. 结合dispatchTouchEvent
和这里的这个判断, 得出, 如果控件是可点击的, 并且没有被过滤掉的, 那么这个控件一定可以消费掉事件.分析 2
走到这一步 无论进入到哪一个if
内, 都会调用performClickInternal()
方法.
可以点进new PerformClick
类看一下, 它实现了Runnable
接口, 在run
方法内调用的也是performClickInternal()
方法.
而在performClickInternal()
方法内部又调用了performClick()
, 下面接着看performClick()
3. View.performClick()
View.java 6588 行.
public boolean performClick() {
...
final boolean result;
final ListenerInfo li = mListenerInfo;
//和 View.dispatchTouchEvent 中判断类似, dispatchTouchEvent 中判断的是 mOnTouchListener, 这里判断是 mOnClickListener
//条件 1: ListenerInfo 对象不为 null
//条件 2: 判断是否通过 setOnClickListener 设置了监听,即是否有实现 OnClickListener,
if (li != null && li.mOnClickListener != null) {
//播放声音, 即点击音效.
playSoundEffect(SoundEffectConstants.CLICK);
//开始响应点击事件,回调我们重写的 onClick 事件.
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
...
return result;
}
这次问题 3 也得到解答了
- onClick 方法会在什么时候被调用.
在
onTouchEvent()
方法中对ACTION_UP
的处理过程中, 才会调用onClick
方法. 如果onTouch
中直接返回了true
, 那么不会调用onTouchEvent
方法, 就更不会调用onClick
了.
知识点:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看到, 如果这个控件本身是不可点击的, 那么会将它先变成可点击的, 所以一般我们只要调用了 `setOnClickListener` 方法, 之前设置的当前控件不可点击就会失效.
返回看 `onTouchEvent` 方法, 我们得出了问题 4 的答案.
- 默认情况下, 控件都是 enable 的, 我们重写了 onClick, onTouch, 但如果设置了 setEnable(false), 会怎么样
首先在
dispatchTouchEvent
方法中, 不会执行onTouch
, 因为判断中的&& (mViewFlags & ENABLED_MASK) == ENABLED
这个条件不成立. 所以不会调我们重写的onTouch
.
接着会调用onTouchEvent
, 但是在onTouchEvent
方法中, 执行到if ((viewFlags & ENABLED_MASK) == DISABLED)
这个判断的时候, 就会进入内部逻辑然后return clickable
了, 这个clickable
状态是true
, 因为我们设置了setOnClickListener
监听, 所以不会继续向下执行ACTION_UP
内的逻辑, 也就不会执行onClick
.结论: 控件都是 enable 的, 我们重写了 onClick, onTouch, 但如果设置了 setEnable(false), 只会调用 onTouchEvent, 不会调用 onTouch 和 onClick
后面一篇文章会对 Android 事件分发进行一个简单的总结.