文章目录
-
- DecorView的Touch事件处理
- Activity中的Touch事件处理函数
-
- PhoneWindow对象superDispatchTouchEvent
- View的Touch事件处理特性
- View控件的Touch事件处理
-
- View的onTouchEvent
- ViewGroup控件的Touch事件处理
分析来自Android8.1.0源码
DecorView的Touch事件处理
我们现在先关注View相关的事件处理,先不关心如何到达View的。后面还会分析如何到达View。
我们先从DecorView的Touch事件处理开始
public class DecorView {
private PhoneWindow mWindow;
@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);
}
}
- 获取
对象的PhoneWindow
对象。Callback
是在PhoneWindow
的Activity
方法中创建的,并且attach
实现了Activity
,并传递给Window.Callback
作为PhoneWindow
的回调。Callback
- 如果不
为cb
,null
没有被销毁,cb
小于0就会执行mFeatureId
对象的Window.Callback
方法。dispatchTouchEvent
运行时,前两个满足,当创建Activity
的时候Activity
实际为-1。所以会调用mFeatureId
对象的Window.Callback
方法,也就是dispatchTouchEvent
实现的方法。Activity
Activity中的Touch事件处理函数
有touch事件来的时候,会调用Activity的dispatchTouchEvent()派发事件,然后会调用getWindow().superDispatchTouchEvent(ev)派发事件,最终会调用布局中的View.
public class Activity extends ContextThemeWrapper
implements Window.Callback {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
}
- 如果是
会调用MotionEvent.ACTION_DOWN
接口onUserInteraction
- 获取
对象,实际为Window
对象。并调用其PhoneWindow
方法,如果处理了直接返回superDispatchTouchEvent
,true
- 如果
对象调用Window
没有处理事件,就直接调用superDispatchTouchEvent
的Activity
方法。onTouchEvent
因为
Window
对象实际为
PhoneWindow
对象,我们继续分析一下
PhoneWindow
的
superDispatchTouchEvent
方法。
PhoneWindow对象superDispatchTouchEvent
public class PhoneWindow extends Window {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
将会调用
DecorView
的
superDispatchTouchEvent
public class DecorView extends FrameLayout {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
最后会调用父类的
dispatchTouchEvent
方法
FrametLaout
继承
ViewGroup
,
ViewGroup
继承自
View
,并复写了View的
dispatchTouchEvent
方法。
总结:
Activity
的
Touch
会调用到
PhoneWindow
的
superDispatchTouchEvent
方法处理
Touch
事件,
PhoneWindow
又交由
DecorView
的
superDispatchTouchEvent
方法处理,
DecorView
会调用其父类的
dispatchTouchEvent
来处理,也就是
ViewGroup
的
dispatchTouchEvent
来处理。
下面我们在分析
ViewGroup
的
dispatchTouchEvent
之前我们先来分析一下
View
的
dispatchTouchEvent
处理
View的Touch事件处理特性
-
中的View
事件一个是Touch
控件的View
事件处理,一个是Touch
控件的ViewGroup
事件处理,二者处理方式有所不同Touch
-
的ViewGroup
事件处理是先由子Touch
控件处理,子View
控件没有消耗,然后有父View控件进行处理.View
- 对于
控件可以通过ViewGroup
的方法拦截onInterceptTouchEvent
事件Touch
-
中的View
事件处理会先调用Touch
分发,然后调用dispatchTouchEvent
处理,未处理会调用onTouch
处理。View控件还是ViewGroup控件处理逻辑都是一致onTouchEvent
View控件的Touch事件处理
View
处理
Touch
事件时首先被调用的是
dispatchTouchEvent()
,此方法的主要工作是调用恰当的方法进行处理.
public class View{
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
}
......
}
}
先调用
onFilterTouchEventForSecurity
方法是否需要过滤事件,我们先来看一下此方法。
public class View {
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
}
检查
View
和
Window
是否被遮盖,如果被遮盖就过滤掉事件。
接着我们继续分析
dispatchTouchEvent
方法。
public class View{
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
......
}
......
}
}
接着检查
ENABLED_MASK
,也就是
View
检查是否
enable
,因为只有
enable
的状态下的
View
才能处理事件。接着调用
handleScrollBarDragging
方法,也就是处理滑动scroll bar事件,如果是,
result
为
true
。
我们继续分析。
public class View{
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
......
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
......
}
......
}
}
ListenerInfo
中封装了各种
Listener
的接口对象,有
OnClickListener
,
OnLongClickListener
,
OnTouchListener
等等。这里是检测是否存在
OnTouchListener
接口对象,也就是我们是否通过
setOnTouchListener
设置了
OnTouchListener
,是否
OnTouchListener
的
onTouch
是否会处理此事件。如果处理
result
为
true
。
接着分析。
public class View{
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
·······
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
}
}
如果不是滑动scroll bar,并且没设置
OnTouchListener
或者设置了
OnTouchListener
但
onTouch
不处理此事件。那就会调用
View
的
onTouchEvent
方法处理。
总结:
View
先判断是否是scroll bar的滚动,然后是调用
OnTouchListener
但
onTouch
,二者皆没有处理就调用
View
的
onTouchEvent
方法进行处理
我们来分析
View
的
onTouchEvent
方法
View的onTouchEvent
View的onTouchEvent比较多我们来一段一段分析
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
......
}
获取事件的作为
x
,
y
值。然后是检测
view
的
flag
是否存在
CLICKABLE
,
LONG_CLICKABLE
,
CONTEXT_CLICKABLE
。是否支持点击,长按点击,以及内容点击。
public boolean onTouchEvent(MotionEvent event) {
······
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
······
}
检测
View
的
flag
的
enable
的状态,如果是
DISABLED
,接着检测检测事件是否是
MotionEvent.ACTION_UP
,并且检测是否是PRESSED,如果是PRESSED,设置pressed的状态为false。
最后是返回
clickable
,说明如果
View
的
enable
是
DISABLED
但是设置相关的click,仍然会消耗此事件。
下面是关于Event的事件分析了。我们知道一次点按的顺序是按下,移动,抬起。因此我们也以这个顺序来分析。
以ACTION_DOWN事件分析
public boolean onTouchEvent(MotionEvent event) {
......
switch (event.getAction()) {
......
case MotionEvent.ACTION_DOWN:
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (isInScrollingContainer) {
} else {
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
}
......
return true;
}
return false;
}
把代码抽取关键部分,检测clickable,若果是false,直接调用checkForLongClick检测长按事件。
接着如果是在滚动的布局中执行相关的操作,这里我们不关心,直接查看不是在滚动布局中。调用
setPressed
设置
pressed
状态,
checkForLongClick
检测长按事件。
setPressed
主要是设置
PFLAG_PRESSED
这里不做分析了。
我们来看一下
checkForLongClick
如何检测长按事件的。
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
检测
LONG_CLICKABLE
是否支持长按,检测
TOOLTIP
是否长按显示提醒。
如果支持一种,就会创建一个
CheckForLongPress
对象。然后通过postDelayed发送一个延迟操作。默认的延迟操作为500ms。我们后面在分析触发长按的情况。
分析ACTION_MOVE
public boolean onTouchEvent(MotionEvent event) {
...
switch (event.getAction()) {
...
case MotionEvent.ACTION_MOVE:
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
}
}
pointInView方法
是否判断坐标是否在View内,一旦移动出
View
范围,就会清除状态,移除长按消息,并设置
pressed
状态为
false
。
分析ACTION_UP
public boolean onTouchEvent(MotionEvent event) {
...
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//如果PRESSED状态下执行
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
//请求焦点
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
//如果长按没有触发,并且忽略下次的up事件为false执行
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//移除长按的监听
removeLongPressCallback();
//仅仅获取焦点才执行
if (!focusTaken) {
//这段代码主要是执行调用OnClickListener的onClick,可以自行分析
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
// 创建重置Pressed的Runnable对象
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
//运行Pressed的Runnable
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
}
break;
}
上面的代码主要的功能是
- 获取焦点
- 长按事件没有执行,调用
的OnClickListener
onClick
- 重置
状态。Pressed
我们还遗留一个问题是长按事件的触发情况,我们来分析一下。
private final class CheckForLongPress implements Runnable {
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
}
判断是否是按下状态等等,最后执行View的
performLongClick
方法。
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
然后调用View的重载的performLongClick方法。
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
然后调用到
View
的
performLongClickInternal
方法。
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
//处理事件
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
//处理contextMenu
if (!handled) {
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
//处理长按时的tip
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
if (!handled) {
handled = showLongClickTooltip((int) x, (int) y);
}
}
return handled;
}
从上面的代码可以知道了先调用
OnLongClickListener
的
onLongClick
方法,没有处理掉事件由contextMenu来处理,没有处理掉最后是长按时的tip。三者是互斥的。
从上面可以总结出click事件的触发的touch事件
- 点击事件: ACTION_DOWN -> ACTION_UP或者ACTION_DOWN -> ACTION_MOVE…-> ACTION_UP(未移动到View之外) 小于500ms or 大于500ms长按没有被处理
- 长按事件: ACTION_DOWN-> ACTION_MOVE…(未移动到View之外) 大于500ms,可以处理。
ViewGroup控件的Touch事件处理
ViewGroup
继承自
View
,覆写
View
的
dispatchTouchEvent()
的方法。我们从很多书上都说
ViewGroup
的
touch
事件处理,先派发事件处理给子
View
控件处理,子
View
事件处理后返回
false
就有
ViewGroup
处理,从源码角度分析。到底是如何实现的。
由于代码太多,分段阅读。
分析ACTION_DOWN事件
先分析事件为
ACTION_DOWN
时。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//1. 初始化状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
.......
}
也是调
onFilterTouchEventForSecurity
判断是否
Event
是否需要过滤。不需要过滤时,接着调用判断是
ACTION_DOWN
时,调用
cancelAndClearTouchTargets
和
resetTouchState
来清理原有状态,保证状态正确。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 2. 检测拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
}
}
检测是否要拦截touch事件, 检测是否是
ACTION_DOWN
,是否
mFirstTouchTarget
(其实这个是处理事件的)为
null
,二者都不满足的话,拦截此事件,有其中一个满足就检测
FLAG_DISALLOW_INTERCEPT
,此
flag
设置,就说明不允许
ViewGroup
拦截事件,没设置最后会调用
onInterceptTouchEvent
来检测拦截。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
......
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//没有拦截,也没有取消
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//获取分发事件的子View的事件
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子View看哪个可以处理事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 先判断子View是否可以接受事件,接着判断是否在子View的区域内。不满足直接继续
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//如果已经有可以处理的target直接返回
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//调用dispatchTransformedTouchEvent来判断子View能否处理事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//设置mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
}
}
这部分主要查找
ViewGroup
的子View来处理事件。查找是通过
canViewReceivePointerEvents
(也就是通过判断可见性)和
isTransformedTouchPointInView
(通过touch事件是否在
View
上)函数。对于消息的处理,如果子
View
消耗了事件,会通过
addTouchTarget
函数设置
mFirstTouchTarget
(后面会直接通过它来处理
ACTION_UP
事件),如果没有子
View
处理事件,
mFirstTouchTarget
会为
null
。
接着分析
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
......
//上面如果没有子View,然后mFirstTouchTarget为null,dispatchTransformedTouchEvent处理
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
final TouchTarget next = target.next;
//上面查找的时候已经处理,因此if满足,handled为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
}
}
}
}
}
到此
ACTION_DOWN
分析完成了。唯一的是不管是子View还是ViewGroup本身都调用了
dispatchTransformedTouchEvent
处理事件,只是child参数不同,ViewGroup传递null,子View传递相应的对象。我们来分析一下此方法。
下面分析一下
dispatchTransformedTouchEvent
到底如何处理事件
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
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;
}
......
}
此段判断是否为是否为cancel,是cancel,如果child不为null,就调用child的dispatchTouchEvent,否则就调用ViewGroup的继承类View的事件处理。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
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);
}
}
判断上次事件的点与现在事件的点就是相同,处理的方式也是相同。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
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);
}
}
处理方式也是相同。
分析ACTION_MOVE
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
......
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
}
}
}
}
对于
ACTION_MOVE
的事件拦截的检测跟
ACTION_DOWN
一致。如果
ACTION_DOWN
没有子View处理,
mFirstTouchTarget
为
null
,
ACTION_MOVE
也是没有子
View
处理的,调用
dispatchTransformedTouchEvent
处理,如果有子
View
,
mFirstTouchTarget
不为
null
,而且
ACTION_MOVE
还没处理,调用子
View
的
dispatchTouchEvent
处理。
ACTION_UP
处理的过程与
ACTION_MOVE
一致。