有这样一个布局,Activity中有一个ViewGroup,ViewGroup中又放了一个View,我重写了Activity的dispatchTouchEvent和onTouchEvent,重写了ViewGroup的dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent,重写了View的dispatchTouchEvent和onTouchEvent,然后触摸一下TestTouchView,看看控制台打印了什么结果
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".test.source.TestViewTouchActivity">
<com.app.rzm.test.view.TestTouchViewGroup
android:layout_width="match_parent"
android:background="#f00"
android:layout_height="400dp">
<com.app.rzm.test.view.TestTouchView
android:layout_width="200dp"
android:background="#0f0"
android:id="@+id/touch"
android:layout_height="200dp" />
</com.app.rzm.test.view.TestTouchViewGroup>
</LinearLayout>
1.默认没有任何拦截和事件处理的情况下打印结果
05-28 08:07:49.305 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:0
05-28 08:07:49.306 1590-1590/com.app.rzm D/ViewGroup: dispatchTouchEvent:0
onInterceptTouchEvent:0
05-28 08:07:49.306 1590-1590/com.app.rzm D/View: dispatchTouchEvent:0
05-28 08:07:49.307 1590-1590/com.app.rzm D/View: onTouchEvent:0
05-28 08:07:49.307 1590-1590/com.app.rzm D/ViewGroup: onTouchEvent:0
05-28 08:07:49.309 1590-1590/com.app.rzm D/Activity: onTouchEvent:0
05-28 08:07:49.327 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:2
onTouchEvent:2
05-28 08:07:49.344 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:2
onTouchEvent:2
05-28 08:07:49.361 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:2
onTouchEvent:2
05-28 08:07:49.377 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:2
05-28 08:07:49.378 1590-1590/com.app.rzm D/Activity: onTouchEvent:2
05-28 08:07:49.394 1590-1590/com.app.rzm D/Activity: dispatchTouchEvent:2
05-28 08:07:49.395 1590-1590/com.app.rzm D/Activity: onTouchEvent:2
可以看到在没有进行事件拦截的情况下,打印的顺序如上,我们可以简单分析以下事件传递流程:
有一点需要注意,事件的按下,滑动和抬起三种类型事件是一个一个进行传递的
1.当手指触摸屏幕时,按下事件收到响应,Activity的dispatchTouchEvent收到回调,将按下事件传递到了ViewGroup中的dispatchTouchEvent方法
2.ViewGroup收到按下事件时,调用onInterceptTouchEvent判断是否拦截按下事件,因为没有设置拦截,所以按下事件又被传递到View的dispatchTouchEvent方法中
3.View收到按下事件,传递到它的onTouchEvent方法去处理,由于这个View并没有设置处理这个事件,所以又将它传递到父布局ViewGroup的onTouchEvent中,看它是不是需要处理
4.ViewGroup的onTouchEvent收到这个事件,仍没有进行处理,所以又将事件传递到了Activity的onTouchEvent方法
5.Activity同样不处理,所以这个事件因为没人处理,所以就不了了之
6.ACTION_DOWN事件没有被处理,ACTION_MOVE事件就一直在Activity的dispatchTouchEvent和onTouchEvent方法间传递,直到ACTION_UP事件响应
源码中事件的传递,首先来到Activity的dispatchTouchEvent中
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这个方法是提供出来进行重写的,不用管
onUserInteraction();
}
//开始分发触摸事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果事件没有被处理,则执行onTouchEvent方法
return onTouchEvent(ev);
}
来到PhoneWindow中的superDispatchTouchEvent,可以看到它调用了DecorView中的方法,通过之前看setContentView的源码(https://www.jianshu.com/p/2f87ebe77f4e)我们已经认识了DecorView,接下来进入DecorView源码中
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
可以看到DecorView又调用了它父类的方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
进入ViewGroup中的dispatchTouchEvent方法,这里是事件分发的源头。我们可以看到在这个方法中,做了许多的条件判断,而最终都会执行方法dispatchTransformedTouchEvent,区别在于方法中传递的参数会根据场景而有所不同
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
//过滤掉一些不安全的劫持式的触摸事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//新的按下事件产生时就初始化所有之前的触摸状态
//检测事件是否被拦截
if (!canceled && !intercepted) {
//获取到当前得到焦点的view
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//获取到所有可以接收这个触摸事件的view封装到集合中
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
......
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
......
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
break;
}
.......
}
.......
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
......
}
}
}
......
return handled;
}
我们来到方法dispatchTransformedTouchEvent中,这个方法的作用就是将触摸事件传递到当前的view中,如果当前view为null,就传递到它的上一层的ViewGroup,最终的是携带的MotionEvent参数,可以看到当存在按下滑动等事件时,这些事件会被封装到MotionEvent中传递到view
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
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();
//如果当前事件是取消,则将取消参数设置给MotionEvent传递到view中
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;
//回调位置信息到下一级的view的dispatchTouchEvent方法
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;
}
事件从ViewGroup分发到了View的dispatchTouchEvent方法,我们进入View的这个方法中,这里涉及到onTouch,onTouchEvent和onClick方法的回调顺序问题,可以在代码中看到,在onClickListener 和onTouchListener都设置了的情况下,最先执行的会是onTouch,并且如果onTouch方法返回值设置为true,那么将不会再往下回调onTouchEvent和onClick方法,事件就被onTouch拦截了;当其返回值为false,则会继续向下开始执行onTouchEvent,onClick方法是在onTouchEvent之后执行的,所以三者的执行顺序,在没有拦截的情况下,可见是onTouch > onTouchEvent > onClick
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
......
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//按下后停止view的滑动
stopNestedScroll();
}
//安全性过滤
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo 对象封装了所有的view的监听相关的信息
//例如onTouchListener onClickListener等,只要给view设置
//了监听,这个对象就会被创建,可以在下一个代码块中
//看到ListenerInfo 的结构类型
ListenerInfo li = mListenerInfo;
//这里会决定onTouch方法是否执行,如果给view设置了
//onTouchListener,那么ListenerInfo 对象存在,li != null满足
//li.mOnTouchListener != null也满足,
//(mViewFlags & ENABLED_MASK) == ENABLED
//用来判断这个view是否可用,如果被设置为enable false,那么
//是无法处理事件的,条件都满足了,就会回调onTouch方法了
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 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
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
OnCapturedPointerListener mOnCapturedPointerListener;
}
进入onTouchEvent方法,这个方法处理的比较复杂,我们删除一些非核心的代码来看.setOnClickListener的onClick方法最后是在抬起手指的时候执行的,所以这里有一个问题,如果你自定义的View重写了onTouchEvent,并且没有调用父类的super.onTouchEvent方法,那么你设置的onClickListener将会失效
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
//检查是否可点击
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
//在手势抬起时执行onClick方法
case MotionEvent.ACTION_UP:
......
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
......
break;
case MotionEvent.ACTION_DOWN:
......
break;
case MotionEvent.ACTION_CANCEL:
......
break;
case MotionEvent.ACTION_MOVE:
......
break;
}
return true;
}
return false;
}
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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
总结一下,到这里touch事件传递机制基本上简单的走了以下流程。事件开始的起点在所触摸的Activity的dispatchTouchEvent方法,通过这个方法将事件通过PhoneWindow传递到DecorView,然后又传递到ViewGroup中,在ViewGroup中进行事件的分发,首先获取到所有可以接收这个事件的View集合,然后将事件传递到当前获取焦点的View,一层一层的传递,经过ViewGroup的dispatchTouchEvent onInterceptTouchEvent方法进入View的dispatchTouchEvent方法,最后进入onTouchEvent方法进行事件的处理