文章目录

  • 前言
  • 一.MotionEvent
  • 二.事件分发
    • 1.代码模型
    • 2.代码分析
      • 0x00.Activity
      • 0x01.ViewGroup
        • 1.是否分发事件
        • 2.事件分发前清除标记
          • 关于TouchTarget mFirstTouchTarget变量
        • 3.是否拦截Touch事件
        • 4.拦截&分发
      • 0x02.View的dispatchTouchEvent
      • 0x03.View的onTouchEvent

前言

事件分发机制在Android体系中是相当重要的,在自定义View的时候可能需要考虑事件分发机制的影响。之前也看过别人写的文章,但是总感觉容易忘记,这里就对Android事件分发机制做一下浅析。

一.MotionEvent

MotionEvent是一个用于记录你接触屏幕后留下的一系列的事件。这些事件用的最多就是这几个ACTION_DOWN,ACTION_MOVE,ACTION_UP。下面罗列些比较常用的几个事件。

事件类型 具体动作
ACTION_DOWN 手指按下
ACTION_MOVE 手指滑动
ACTION_UP 手指抬起
ACTION_CANCEL 事件被拦截
ACTION_OUTSIDE 超出区域

一般的事件顺序是:
(1).ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->-…->ACTION_UP
(2).ACTION_DOWN->ACTION_UP
对于ACTION_DOWN,ACTION_MOVE,ACTION_UP这些点击事件,大家其实已经很熟悉了。但是在看代码的时候,也会看到这两个事件ACTION_CANCEL 和 ACTION_OUTSIDE。通过MotionEvent详解这篇文章所述。
(1)ACTION_CANCEL:是只有上层 View 回收事件处理权的时候,ChildView 才会收到一个 ACTION_CANCEL 事件。
(2)ACTION_OUTSIDE:在特殊情况下,比如Dialog(没有占满屏幕大小的窗口)能够接收到视图区域外的事件。

二.事件分发

1.代码模型

在研究代码之前,先建立一个模型,先上代码。
xml代码:

<?xml version="1.0" encoding="utf-8"?>
<com.example.view.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:background="@color/colorPrimary"><com.example.view.LayoutView2android:layout_width="350dp"android:layout_height="350dp"android:gravity="center"android:background="@color/colorAccent"><com.example.view.MyTextViewandroid:layout_width="200dp"android:layout_height="200dp"android:gravity="center"android:background="@android:color/holo_green_dark" /></com.example.view.LayoutView2></com.example.view.LayoutView1>

从xml来看LayoutView1在最外层,LayoutView2在中间一层,MyTextView在最里面一层。界面效果如下:

java代码:

// LayoutView1
public class LayoutView1 extends LinearLayout {public LayoutView1(Context context) {super(context);}public LayoutView1(Context context, AttributeSet attrs) {super(context, attrs);}public LayoutView1(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.e("test", "LayoutView1::dispatchTouchEvent!");return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.e("test", "LayoutView1::onInterceptTouchEvent!");return false;}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e("test", "LayoutView1::onTouchEvent!");return super.onTouchEvent(event);}
}// LayoutView2
public class LayoutView2 extends LinearLayout {public LayoutView2(Context context) {super(context);}public LayoutView2(Context context, AttributeSet attrs) {super(context, attrs);}public LayoutView2(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.e("test", "LayoutView2::dispatchTouchEvent!");return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.e("test", "LayoutView2::onInterceptTouchEvent!");return false;}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e("test", "LayoutView2::onTouchEvent!");return super.onTouchEvent(event);}
}// MyTextView
public class MyTextView extends TextView {public MyTextView(Context context) {super(context);}public MyTextView(Context context, AttributeSet attrs) {super(context, attrs);}public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Log.e("test", "MyTextView::dispatchTouchEvent!");return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e("test", "MyTextView::onTouchEvent!");return true;}
}

在LayoutView1,LayoutView2的onInterceptTouchEvent都返回false的时候:

在LayoutView1的onInterceptTouchEvent返回true,LayoutView2的onInterceptTouchEvent返回false的时候:

在LayoutView1的onInterceptTouchEvent返回false,LayoutView2的onInterceptTouchEvent返回true的时候:

等等。。。
还有其他的匹配选择,那么在这里总结下规律:

  1. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
  2. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
  3. 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
  4. 如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

该段引自onInterceptTouchEvent和onTouchEvent调用时序

2.代码分析

0x00.Activity

为了能够详细的了解事件分发的规律,下面对事件分发的代码做一个分析。
“事件分发”除了是事件分发是从最外层分发到最内层,还要经过Activity的dispatchTouchEvent方法做事件分发。

 // Activitypublic boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}// 重点代码if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}

调用了Activity的dispatchTouchEvent方法后,再调用了PhoneWindow的superDispatchTouchEvent方法,这里的getWindow方法获取的就是PhoneWindow的实例。

 // PhoneWindow@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}

又调用了DecorView的superDispatchTouchEvent方法,再来看看superDispatchTouchEvent方法。

 public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}

要知道DecorView的父类是ViewGroup,所以这里就是调用了ViewGroup的dispatchTouchEvent方法。至此我们开始进入ViewGroup类。

0x01.ViewGroup

下面可以通过dispatchTouchEvent代码可以大概归纳以下的一些分发流程。

1.是否分发事件

     // 测试程序if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}// 和Android的无障碍服务(AccessibilityService)有关if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {ev.setTargetAccessibilityFocus(false);}boolean handled = false;// 首先判断该View是否需要执行后面的分发逻辑if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// 如果当前是ACTION_DOWN操作,就清除触摸操作的所有痕迹if (actionMasked == MotionEvent.ACTION_DOWN) {// 清除触摸操作的所有痕迹cancelAndClearTouchTargets(ev);resetTouchState();}

这里主要的逻辑在于onFilterTouchEventForSecurity方法,该方法会判断是否需要执行下面的逻辑,也就是是否需要分发事件
onFilterTouchEventForSecurity代码:

// 依据安全策略,过滤触摸事件
public boolean onFilterTouchEventForSecurity(MotionEvent event) {// 判断是否设置了被遮挡的时候是否处理触摸事件// 判断当前的View是否被其他View遮挡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;}

FILTER_TOUCHES_WHEN_OBSCURED:设置被遮挡时是否处理触摸事件,该变量可以通过android:filterTouchesWhenObscured来设置。

FLAG_WINDOW_IS_OBSCURED:该事件的窗口是否被其它窗口遮挡
由此可见,如果包含FILTER_TOUCHES_WHEN_OBSCURED和FLAG_WINDOW_IS_OBSCURED,则不会向下分发点击事件。

2.事件分发前清除标记

 // ACTION_DOWN的时候清理TouchTargets和Touch状态if (actionMasked == MotionEvent.ACTION_DOWN) {// 清理上次事件的状态cancelAndClearTouchTargets(ev);// 重置ViewGroup的Touch状态resetTouchState();}

首先看下cancelAndClearTouchTargets方法:

private void cancelAndClearTouchTargets(MotionEvent event) {// 判断mFirstTouchTarget是否为null// 通过调试代码发现该View从Window中移除的时候mFirstTouchTarget为nullif (mFirstTouchTarget != null) {boolean syntheticEvent = false;// 判断event是否为nullif (event == null) {// 人工包装一个ACTION_CANCEL的事件final long now = SystemClock.uptimeMillis();event = MotionEvent.obtain(now, now,MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);event.setSource(InputDevice.SOURCE_TOUCHSCREEN);syntheticEvent = true;}// 遍历TouchTarget链表for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {// 重置PFLAG_CANCEL_NEXT_UP_EVENT常量// 该常量用于View在与ViewGroup解绑的时候起到标记作用resetCancelNextUpFlag(target.child);// 向子View分发ACTION_CANCEL事件// cancel为true,所以无论是什么事件都会被置为ACTION_CANCEL事件dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);}// 清空TouchTargets链表clearTouchTargets();// 释放MotionEventif (syntheticEvent) {event.recycle();}}}private static boolean resetCancelNextUpFlag(@NonNull View view) {// 首先判断是否包含PFLAG_CANCEL_NEXT_UP_EVENT,再重置该标记if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;return true;}return false;}private void clearTouchTargets() {// 遍历TouchTarget链表并释放链表资源TouchTarget target = mFirstTouchTarget;if (target != null) {do {TouchTarget next = target.next;target.recycle();target = next;} while (target != null);mFirstTouchTarget = null;}}

resetTouchState方法

 private void resetTouchState() {// 遍历TouchTarget链表并释放链表资源clearTouchTargets();// 重置该ViewGroup的PFLAG_CANCEL_NEXT_UP_EVENT标记resetCancelNextUpFlag(this);// 重置FLAG_DISALLOW_INTERCEPTmGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;mNestedScrollAxes = SCROLL_AXIS_NONE;}
关于TouchTarget mFirstTouchTarget变量

首先看下TouchTarget类

private static final class TouchTarget {// ...省略代码//子Viewpublic View child;//由该目标捕获的所有的手指的IDS 的结合位掩码public int pointerIdBits;//用于指向链表中的下一个TouchTargetpublic TouchTarget next;// ...省略代码
}

这个类最重要的是child变量和next变量,前者是被触摸的子View指针,后者则是指向下一个TouchTarget的地址,从这里可以看出把ViewGroup的子View和子View的信息封装成一个TouchTarget链表。mFirstTouchTarget通过字面意思也知道是链表的头,通过链表头可以遍历整个链表。

3.是否拦截Touch事件

         // 是否拦截Touch事件final boolean intercepted;// ACTION_DOWN事件或者mFirstTouchTarget不为nullif (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {// disallowIntercept和requestDisallowInterceptTouchEvent有关// 如果设置为true代表子View不希望父View拦截Touch事件final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;// disallowIntercept默认为false// true的时候代表子View不希望父View拦截Touch事件if (!disallowIntercept) {// 调用自己的onInterceptTouchEvent,来决定是否拦截touch事件intercepted = onInterceptTouchEvent(ev);ev.setAction(action);} else {intercepted = false;}} else {intercepted = true;}

这段代码中一个重要的常量就是FLAG_DISALLOW_INTERCEPT,这个常量和requestDisallowInterceptTouchEvent方法有关。

requestDisallowInterceptTouchEvent代码:

 @Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parentif (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}}

从代码可以看出disallowIntercept设置为true的时候mGroupFlags添加一个FLAG_DISALLOW_INTERCEPT常量。ViewGroup中的(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0代码判断是否被设置了FLAG_DISALLOW_INTERCEPT,如果被设置了,那么intercepted为false,代表不去拦截Touch事件,同时向子View分发Touch事件。

4.拦截&分发

首先是关于事件取消的判断

// 包含PFLAG_CANCEL_NEXT_UP_EVENT或者ACTION_CANCEL
final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// 是否包括PFLAG_CANCEL_NEXT_UP_EVENT
private static boolean resetCancelNextUpFlag(@NonNull View view) {if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;return true;}return false;}

接下来是分发事件

// 暂时不清楚
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;// TouchTarget链表TouchTarget newTouchTarget = null;// 判断事件是否被消费boolean alreadyDispatchedToNewTouchTarget = false;// 前面的canceled和intercepted已经说明// canceled是否取消事件// intercepted是否拦截// canceled或者intercepted为falseif (!canceled && !intercepted) {// Accessibility辅助功能View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;// ACTION_DOWN或者ACTION_POINTER_DOWN,ACTION_HOVER_MOVEif (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {// 获取当前触摸手指在多点触控中的排序// 这个值可能因为有手指发生Down或Up而发生改变final int actionIndex = ev.getActionIndex(); // always 0 for down// 标识当前是那一个点的触摸事件final int idBitsToAssign = split ? 1 <<// 此时获取到手指的Id,这个值在Down到Up这个过程中是不会改变的
ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;// Clean up earlier touch targets for this pointer id in case they// 清理之前触摸事件中的目标removePointersFromTouchTargets(idBitsToAssign);// 子View的数量final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {// 通过actionIndex获取X,Yfinal float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// Find a child that can receive the event.// Scan children from front to back.final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();// 所有子View组成的数组final View[] children = mChildren;// 遍历子Viewfor (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);// 获取子Viewfinal View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);// 和Accessibility服务相关if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// Child is already receiving touch within its bounds.// Give it the new pointer in addition to the ones it is handling.newTouchTarget.pointerIdBits |= idBitsToAssign;break;}// 判断是否包含PFLAG_CANCEL_NEXT_UP_EVENT,如果包含就去除该常量resetCancelNextUpFlag(child);// 分发事件,重要方法if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {// childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();// 分发成功后封装一个TouchTarget同时添加到链表newTouchTarget = addTouchTarget(child, idBitsToAssign);// touch事件分发成功修改为truealreadyDispatchedToNewTouchTarget = true;break;}// The accessibility focus didn't handle the event, so clear// the flag and do a normal dispatch to all children.ev.setTargetAccessibilityFocus(false);}if (preorderedList != null) preorderedList.clear();}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}

通过上面的代码可知道,如果不拦截touch事件,就去执行dispatchTransformedTouchEvent方法。现在我们看下dispatchTransformedTouchEvent方法。

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();// 如果cancel为true,则进入下面的逻辑,分发所有的View一个cancel的touch事件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;}

这里的"cancel"为false

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;}

那么会主要执行这段逻辑,判断childView不为null的情况下,调用childView的dispatchTouchEvent;如果为null,则调用父View的dispatchTouchEvent事件。

     // Perform any necessary transformations and dispatch.if (child == null) {// chlidView为null就执行ViewGroup父类View的dispatchTouchEventhandled = 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());}// 调用childView的dispatchTouchEventhandled = child.dispatchTouchEvent(transformedEvent);}

总之,无论如何都会调用View的dispatchTouchEvent方法。接下来就会调用View的onTouchEvent方法。

0x02.View的dispatchTouchEvent

View里面的dispatchTouchEvent方法相对比较简单,代码如下:

public boolean dispatchTouchEvent(MotionEvent event) {// Accessibility辅助功能相关// If the event should be handled by accessibility focus first.if (event.isTargetAccessibilityFocus()) {// We don't have focus or no virtual descendant has it, do not handle the event.if (!isAccessibilityFocusedViewOrHost()) {return false;}// We have focus and got the event, then use normal event dispatch.event.setTargetAccessibilityFocus(false);}boolean result = false;if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}final int actionMasked = event.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {// Defensive cleanup for new gesturestopNestedScroll();}// 和ViewGroup的onFilterTouchEventForSecurity的作用一样,可以参考前面的分析if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}// =====================重要的核心代码=====================//noinspection SimplifiableIfStatementListenerInfo 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;}}// =====================重要的核心代码=====================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;}

重点看下面的代码:

// 和ViewGroup的onFilterTouchEventForSecurity的作用一样,可以参考前面的分析
if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo 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;}}

注意到li.mOnTouchListener.onTouch(this, event)代码,从这段代码可以看出,如果要执行View的onTouchEvent方法,需要保证复写的onTouch接口返回值为false(事件未被消费,向下传递),这样才可以调用View的onTouchEvent方法。那么接下来就进入到onTouchEvent方法中。

0x03.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();// 判断是否有CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE事件final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;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;}// 和TouchDelegate相关的,该类可以用来扩大View的点击区域if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}// 用来判断ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE相关的touch事件if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;if ((viewFlags & TOOLTIP) == TOOLTIP) {handleTooltipUp();}if (!clickable) {removeTapCallback();removeLongPressCallback();mInContextButtonPress = false;mHasPerformedLongPress = false;mIgnoreNextUpEvent = false;break;}boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {// take focus if we don't have it already and we should in// touch mode.boolean focusTaken = false;if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {focusTaken = requestFocus();}if (prepressed) {// The button is being released before we actually// showed it as pressed.  Make it show the pressed// state now (before scheduling the click) to ensure// the user sees it.setPressed(true, x, y);}if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// This is a tap, so remove the longpress checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!focusTaken) {// Use a Runnable and post this rather than calling// performClick directly. This lets other visual state// of the view update before click actions start.if (mPerformClick == null) {mPerformClick = new PerformClick();}// 核心代码if (!post(mPerformClick)) {performClickInternal();}// 核心代码}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();}if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {// If the post failed, unpress right nowmUnsetPressedState.run();}removeTapCallback();}mIgnoreNextUpEvent = false;break;case MotionEvent.ACTION_DOWN:if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {mPrivateFlags3 |= PFLAG3_FINGER_DOWN;}mHasPerformedLongPress = false;if (!clickable) {checkForLongClick(0, x, y);break;}if (performButtonActionOnTouchDown(event)) {break;}// Walk up the hierarchy to determine if we're inside a scrolling container.boolean isInScrollingContainer = isInScrollingContainer();// For views inside a scrolling container, delay the pressed feedback for// a short period in case this is a scroll.if (isInScrollingContainer) {mPrivateFlags |= PFLAG_PREPRESSED;if (mPendingCheckForTap == null) {mPendingCheckForTap = new CheckForTap();}mPendingCheckForTap.x = event.getX();mPendingCheckForTap.y = event.getY();postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {// Not inside a scrolling container, so show the feedback right awaysetPressed(true, x, y);checkForLongClick(0, x, y);}break;case MotionEvent.ACTION_CANCEL:if (clickable) {setPressed(false);}removeTapCallback();removeLongPressCallback();mInContextButtonPress = false;mHasPerformedLongPress = false;mIgnoreNextUpEvent = false;mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;break;case MotionEvent.ACTION_MOVE:if (clickable) {drawableHotspotChanged(x, y);}// Be lenient about moving outside of buttonsif (!pointInView(x, y, mTouchSlop)) {// Outside button// Remove any future long press/tap checksremoveTapCallback();removeLongPressCallback();if ((mPrivateFlags & PFLAG_PRESSED) != 0) {setPressed(false);}mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;}break;}return true;}return false;}

注:TouchDelegate相关的可以参考我写的文章:TouchDelegate的用法
这里重点看ACTION_UP事件中的performClickInternal相关代码

private boolean performClickInternal() {// Must notify autofill manager before performing the click actions to avoid scenarios where// the app has a click listener that changes the state of views the autofill service might// be interested on.notifyAutofillManagerOnClick();return performClick();}

再进入到performClick方法,performClick方法代码如下:

public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);// 这里调用了onClick回调方法li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}

综上,通过代码可以知道在执行ACTION_UP后调用OnClickListener的onClick回调函数,由此可知,整个调用onClick回调的流程如下:
onTouch->onTouchEvent->onClick
至此对于整个事件分发流程的简单分下告一段落了,如果有什么纰漏的,欢迎在评论区留言!

参考文章:
你不能错过的View事件分发机制分析
onInterceptTouchEvent和onTouchEvent调用时序
Android Touch事件学习系列汇总
你还在被触摸事件困扰吗?看看这篇吧
触摸事件的分发(ViewGroup篇之一)

Android事件分发机制浅析相关推荐

  1. android触摸事件分发,Android 事件分发机制

    Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过.今天下定决心了解一下,以便后面自己定制 View 效果.Android 触摸事件有三个基本类型:ACTION_DOW ...

  2. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  3. 浅谈Android事件分发机制

    在Android实际开发过程中经常会遇到View之间的滑动冲突,如ScrollView与Listview.RecyclerView之间的嵌套使用.在很好的解决此类问题之前,我们应深入的了解Androi ...

  4. Android 事件分发机制

    Android 事件分发机制  demo验证:  https://blog.csdn.net/hty1053240123/article/details/77866302 目录 1.基础认知 2.事件 ...

  5. Android事件分发机制:基础篇:最全面、最易懂

    如何提升安卓水平?安卓开发者必须了解的事件分发机制. 最全面.最易懂的形式来讲解Android事件分发机制. 0. 前言 鉴于安卓分发机制较为复杂,故分为多个层次进行讲解,分别为基础篇.实践篇与高级篇 ...

  6. Android 系统(199)---Android事件分发机制详解

    Android事件分发机制详解 前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析. ...

  7. android系统(8)---android事件分发机制

    前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天 ...

  8. 一篇文章彻底搞懂Android事件分发机制

    本文讲的是一篇文章彻底搞懂Android事件分发机制,在android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要我们深入的了解 ...

  9. Android 事件分发机制分析及源码详解

    Android 事件分发机制分析及源码详解 文章目录 Android 事件分发机制分析及源码详解 事件的定义 事件分发序列模型 分发序列 分发模型 事件分发对象及相关方法 源码分析 事件分发总结 一般 ...

最新文章

  1. [我研究]Behavior Based Software Theft Detection - Hawk
  2. [css] body{height:100%}和html,body{height:100%}有什么区别?为什么html要设置height:100%呢,html不就是整个窗口吗?
  3. UIControl IOS控件编程
  4. Sqlserver常用函数例子说明
  5. [POJ1961 Period]
  6. Java IO实战操作(四)
  7. mysql mysqld.log_MySQL mysqlbinlog 读取mysql-bin文件出错
  8. js上传文件转二进制格式
  9. 自动化接口实战(一)
  10. 2021-09-29 关于间断点相关题目的总结
  11. JS导入Excel实战
  12. git remote prune
  13. kubernete编排技术五:DaemonSet
  14. 学习LDPC码的一些入门笔记
  15. PEP8中文翻译(转)
  16. 淘宝可以传照片搜索商品,verygood.雅客VC多味水果糖
  17. ai怎样导出所选部分,AI 怎么导出部分文件
  18. 惠普台式计算机型号怎么看,惠普电脑怎么看型号
  19. ZEMAX | 如何编写 ZPL 宏:计算环带垂轴色差
  20. 高效开发(一):装机必备

热门文章

  1. 程序设计思维与实践 Week15 作业A - ZJM 与霍格沃兹
  2. Kutools for Excel 结合 300 多种高级功能和工具
  3. 最新哔哩哔哩视频弹幕播放器源码+带后台/亲测无错误
  4. html文件右键没有打开方式,一个文件打不开,点右键,怎么在打开方式中加入Word,Excel的打开方式,打开方式中有Word的打开方式?...
  5. mac 邮箱客户端之腾讯企业邮箱设置 无法验证账号或密码
  6. Python学习(13)--Lambda表达式和switch语句的实现
  7. 【深入理解TcaplusDB技术】入门Tcaplus SQL Driver
  8. 无忧计算机二级试题题库,计算机等级考试题库,二级Web试题
  9. 一体化伺服电机编码器值清零或设置原点如何操作?
  10. 开机自启动bat脚本,并显示Dos窗口