参考文献
- https://juejin.cn/post/6844904041487532045
- https://www.jianshu.com/p/dea72779a6b7
- https://yanfukun.com/read/anroid-dev1/event#68qq20
文章目录
一. 基本概念
1. Activity的构成

2. 触摸事件的类型
触摸事件对应的是MotionEvent类,事件的类型主要有如下:
- ACTION_DOWN:手指按下屏幕
- ACTION_MOVE:手指在屏幕上移动
- ACTION_UP:手指离开屏幕
- ACTION_CANCEL:事件被取消(如被父View拦截)
3. 事件传递的三个阶段
分发(Dispatch):事件从顶层视图向下传递到目标视图的过程
拦截(Intercept):父视图决定是否拦截事件,不再向子视图传递
处理(Handle):最终接收事件的视图处理触摸事件
4. 视图层次与事件传递方向
Android界面是由视图树(View Tree)组成的,从Activity的根视图(通常是DecorView)开始,经过各级ViewGroup,最终到达叶子节点View。事件传递的基本方向是:
- 分发阶段:自顶向下(从父到子)
- 处理阶段:自底向上(从子到父)
二.事件传递核心机制理解
1.核心方法的作用及返回true/false/super的含义

2. 三个方法之间的关系(伪代码)
/**
* 点击事件产生后
*/
// 步骤1:调用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false; //代表 是否会消费事件
// 步骤2:判断是否拦截事件
if (onInterceptTouchEvent(ev)) {
// a. 若拦截,则将该事件交给当前View进行处理
// 即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
// b. 若不拦截,则将该事件传递到下层
// 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
// 直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
// 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
return consume;
}
3. Activity/ViewGroup/View的差异
Activity:拥有分发和消费两个方法。
ViewGroup:拥有分发、拦截和消费三个方法。
View:拥有分发、消费两个方法。
4.事件传递的优先级
OnTouchListener > onTouchEvent > OnClickListener
如果OnTouchListener返回true,onTouchEvent和OnClickListener不会执行
onClickListener 是基于 onTouchEvent 实现的高级封装
5. 事件序列的特殊处理
对于一个完整的事件序列(从DOWN到UP),有几个重要的特性:
5.1 DOWN事件决定后续事件走向
- 如果ViewGroup在DOWN事件中返回true拦截,则整个事件序列都会交由它处理
- 子View将收到一个ACTION_CANCEL事件
- 为什么这么设计?
这是为了保证事件序列的一致性。如果 DOWN 事件被某个父容器处理,那么它应该拥有处理整个手势(如滑动、抬起)的权利。
如果父容器不拦截 DOWN 事件,后续的 MOVE 和 UP 事件可能会被子 View 处理,这样手势逻辑就可能混乱。
5.2 MOVE事件的动态拦截
- ViewGroup可以在MOVE事件中开始拦截(即使之前没拦截DOWN)
- 此时子View会收到ACTION_CANCEL,后续事件交给ViewGroup
- 应用场景举例:横向滑动的ViewPager与内部纵向滑动的RecyclerView嵌套。当用户开始横向滑动时,ViewPager会拦截事件并处理,此时内部的RecyclerView会收到ACTION_CANCEL;如果用户只轻微纵向滑动,则RecyclerView继续处理事件
5.3 事件一旦被消费,后续同序列事件优先交给消费者
- 如果View消费了DOWN事件,后续MOVE和UP事件会优先交给它处理
- 不再重新执行完整的分发流程
- 这种设计提升了性能,也简化了事件处理逻辑.但 ViewGroup 仍保留中途拦截的权利(通过onInterceptTouchEvent)
6. 常见属性对事件传递的影响
6.1 clickable和focusable属性
当这些属性设置为true时:
- View的onTouchEvent()方法会返回true(消费事件)
- 即使没有设置OnClickListener,View也会消费触摸事件
- 这会阻止事件向上传递给父View
6.2 enabled属性
enabled = false
- OnTouchListener不会被调用
- 但onTouchEvent()仍会被调用,只是不会有视觉反馈
6.3 visibility属性
- 不可见的View(INVISIBLE)不会接收触摸事件
- 完全隐藏的View(GONE)不会接收触摸事件,且不占用空间
6.4 FLAG_DISALLOW_INTERCEPT标志
- 子View可以通过调用父ViewGroup的requestDisallowInterceptTouchEvent()方法,要求父View不要拦截事件:
- 这个标志只对ACTION_DOWN之后的事件有效,因为ViewGroup在收到ACTION_DOWN时会重置这个标志。
7. 事件传递的核心原则
7.1 责任链模式
Android事件系统本质上是一个责任链模式的实现,事件沿着预定路径传递,直到被处理或到达终点
7.2 事件的完整性
触摸事件是成系列的,从DOWN开始,到UP结束。一旦某个View决定处理这个序列中的一个事件,后续事件会优先交给它。
7.3 返回值的意义
返回true:表示事件已被消费,不再传递
返回false:表示事件未被消费,继续传递
7.4 拦截的时机
通常在DOWN事件中不拦截,而在MOVE事件中根据需要动态拦截,这样可以保证事件的连贯性。
7.5 一些结论的补充
三. 事件传递流程图
1. 基本流程图
┌─────────────────────────────────────┐
│ Activity │
│ boolean dispatchTouchEvent() │
└───────────────┬─────────────────────┘
↓
┌─────────────────────────────────────┐
│ PhoneWindow/DecorView │
│ boolean dispatchTouchEvent() │
└───────────────┬─────────────────────┘
↓
┌─────────────────────────────────────┐
│ 根ViewGroup │
│ boolean dispatchTouchEvent() │
└───────────────┬─────────────────────┘
↓
┌─────────────────────────────────────┐
│ 是否拦截? onInterceptTouchEvent() │
└───────────────┬─────────────────────┘
↓
┌────────┴─────────┐
↓是 ↓否
┌─────────────┐ ┌─────────────────────────┐
│自己处理事件 │ │分发给子View/ViewGroup │
└──────┬──────┘ └─────────────┬───────────┘
↓ ↓
┌─────────────┐ ┌──────────┴──────────┐
│ onTouchEvent│ │子View.dispatchTouch │
└─────────────┘ └──────────┬──────────┘
↓
┌──────────┴──────────┐
│重复以上ViewGroup流程 │
└──────────┬──────────┘
↓
┌──────────┴──────────┐
│最终到达目标子View │
│ boolean onTouchEvent│
└─────────────────────┘
注意点:
- 事件回溯:如果目标View的onTouchEvent()返回false(不消费事件),事件会向上传递给父View的onTouchEvent()处理。如此层层上传,直到有View处理或到达Activity
2. 完整流程分析

3. 基于源码分析的完整流程

四. 源码分析
4.1 View类的dispatchTouchEvent源码分析
public boolean dispatchTouchEvent(MotionEvent event) {
// 处理辅助功能相关
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
- 如果设置了OnTouchListener且View是启用状态,先调用OnTouchListener
- 如果OnTouchListener返回true,事件被消费,方法结束
- 否则调用自身的onTouchEvent方法处理事件
4.2 View类的onTouchEvent源码分析
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
// 如果View是禁用的,但可点击,仍然消费事件,只是不响应
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 禁用状态下仍返回是否可点击
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
// 如果设置了代理,交给代理处理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 检查View是否可点击(clickable、longClickable或contextClickable)
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 处理点击事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 移除长按回调
removeLongPressCallback();
// 只有不是长按才执行点击
if (!focusTaken) {
// 使用点击声音和震动反馈
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
// 重置状态
setPressed(false);
}
mIgnoreNextUpEvent = false;
return true;
case MotionEvent.ACTION_DOWN:
// 记录按下状态
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
return true;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// 处理可点击控件的按下效果
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, x, y);
}
return true;
case MotionEvent.ACTION_CANCEL:
// 取消触摸,重置状态
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return true;
case MotionEvent.ACTION_MOVE:
// 处理移动事件,检查是否移出了View范围
if (clickable) {
drawableHotspotChanged(x, y);
}
// 检查是否移出按下区域
if (!pointInView(x, y, mTouchSlop)) {
// 移出区域,移除按下状态
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
return true;
}
// 如果可点击,消费所有类型的事件
return true;
}
// 不可点击,不消费事件
return false;
}
onTouchEvent的核心逻辑:
- 首先检查View是否启用(enabled)
- 然后检查是否可点击(clickable/longClickable/contextClickable)
- 根据不同的事件类型执行相应操作:
DOWN:记录按下状态,启动长按检测
MOVE:更新热点位置,检查是否移出范围
UP:触发点击监听器
CANCEL:重置所有状态 - 关键点:如果View是可点击的,它会消费所有类型的触摸事件
4.3 ViewGroup类的dispatchTouchEvent源码分析
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
// 处理事件开始和结束的标记
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 检查子View是否禁止父View拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 调用onInterceptTouchEvent判断是否拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // 恢复可能被修改的action
} else {
// 子View禁止拦截
intercepted = false;
}
} else {
// 已经开始处理事件序列且没有目标子View,直接拦截
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;
// 如果不取消且不拦截,尝试分发给子View
if (!canceled && !intercepted) {
// ACTION_DOWN时寻找可处理事件的子View
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
// 清除之前的触摸目标
if (actionMasked == MotionEvent.ACTION_DOWN) {
mFirstTouchTarget = null;
}
// 寻找可以接收事件的子View
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
// 从最上层的子View开始检查(绘制顺序的逆序)
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(preorderedList, children, i);
// 如果子View不能接收事件,跳过
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// 分发给子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 子View处理了事件,记录为目标
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
// 如果没有找到新的触摸目标,检查已存在的触摸目标
if (mFirstTouchTarget == null) {
// 没有子View处理,传递给自己的onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 根据已记录的触摸目标分发事件
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 分发给记录的子View
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
// 需要取消子View的事件
if (target.pointerIdBits == oldPointerIdBits) {
removePointersFromTouchTargets(oldPointerIdBits);
}
}
}
target = next;
}
}
}
// 更新触摸状态
if (mFirstTouchTarget == null) {
// 没有子View处理,自己处理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
}
return handled;
}
ViewGroup.dispatchTouchEvent的核心逻辑:
- 决定是否拦截事件(通过onInterceptTouchEvent)
- 如果不拦截且是DOWN事件,寻找可处理事件的子View
- 如果找到目标子View,记录下来,后续事件会直接分发给它
- 如果没有子View处理或拦截了事件,调用自己的onTouchEvent处理
4.4 ViewGroup的onInterceptTouchEvent源码分析
public boolean onInterceptTouchEvent(MotionEvent ev) {
// ev.isFromSource(InputDevice.SOURCE_MOUSE) 的意思是判断当前的输入事件是否是由鼠标产生的。
// ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY):判断是否按下了鼠标的主按钮(通常是左键)。
// isOnScrollbarThumb(ev.getX(), ev.getY()):判断点击位置是否在滚动条的滑块上
if (ev.isFromSource(InputDevice.SOURCE_MOUSE) &&
ev.getAction() == MotionEvent.ACTION_DOWN &&
ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) &&
isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
- 只有鼠标在滚动条上点击时才拦截
- 其他情况都返回false,不拦截事件
4.5 mFirstTouchTarget分析
mFirstTouchTarget 是 ViewGroup 中用于记录当前事件序列的目标子 View 的一个链表头节点。它是一个 TouchTarget 类型的对象,本质上是一个单链表结构。
- 一旦某个子 View 成为 mFirstTouchTarget,后续事件将直接发送给它
- ViewGroup 仍可以在 MOVE 中拦截,此时会清空 mFirstTouchTarget 并发送 ACTION_CANCEL。
- mFirstTouchTarget 的工作流程
当一个 ACTION_DOWN 事件到达 ViewGroup 时:
ViewGroup 会遍历所有子 View,寻找可以接收事件的 View(根据点击区域、是否消费事件等判断)。
如果某个子 View 消费了 ACTION_DOWN(返回 true),则 ViewGroup 会创建一个 TouchTarget 对象,并赋值给 mFirstTouchTarget。
后续的 ACTION_MOVE 和 ACTION_UP 事件将直接发送给这个 TouchTarget 对象所指向的 View,不再重新分发给其他子 View。
如果 **ViewGroup 决定中途拦截事件(在 onInterceptTouchEvent() 返回 true),则会清空 mFirstTouchTarget,**并发送 ACTION_CANCEL 给原目标 View。
五. 常见问题及解决方案
5.1 滑动冲突的解决
外部拦截法
由父容器决定是否拦截事件:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// DOWN事件通常不拦截,记录初始位置
mLastX = ev.getX();
mLastY = ev.getY();
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
// 根据移动方向判断是否拦截
float deltaX = Math.abs(ev.getX() - mLastX);
float deltaY = Math.abs(ev.getY() - mLastY);
// 例如:水平滑动时拦截
if (deltaX > deltaY && deltaX > mTouchSlop) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
// 记录上次触摸位置
mLastX = ev.getX();
mLastY = ev.getY();
return intercepted;
}
外部拦截法的特点:
- 父容器主动判断并拦截
- 在ACTION_MOVE中动态决定是否拦截
- 适合处理方向性冲突(如垂直与水平滑动)
内部拦截法
由子View决定是否允许父容器拦截
// 子View的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 请求父容器不要拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
// 根据自己的判断,是否允许父容器拦截
float deltaX = Math.abs(event.getX() - mLastX);
float deltaY = Math.abs(event.getY() - mLastY);
// 例如:如果是垂直滑动,允许父容器拦截
if (deltaY > deltaX && deltaY > mTouchSlop) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
mLastX = event.getX();
mLastY = event.getY();
return super.dispatchTouchEvent(event);
}
同时,父容器需要处理:
// 父容器的onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 默认拦截除了ACTION_DOWN以外的所有事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
// 其余事件的拦截权交给子View通过requestDisallowInterceptTouchEvent控制
return true;
}
内部拦截法的特点:
- 子View主动控制父容器是否可以拦截,requestDisallowInterceptTouchEvent方法
- 需要父容器配合,默认拦截除DOWN以外的所有事件
- 适合子View需要优先处理事件的场景
5.2 点击与长按冲突
setOnLongClickListener返回true
// 通过设置长按监听器和点击监听器共存
view.setOnLongClickListener(v -> {
// 处理长按
return true; // 消费长按事件
});
view.setOnClickListener(v -> {
// 处理点击
});
5.3 滑动与点击冲突
记录滑动的距离,在ACTION_MOVE和ACTION_UP方法中进行分支处理
// 使用距离阈值区分滑动和点击
private float mLastX, mLastY;
private static final int TOUCH_SLOP = ViewConfiguration.get(context).getScaledTouchSlop();
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = event.getX();
mLastY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(event.getX() - mLastX);
float deltaY = Math.abs(event.getY() - mLastY);
if (deltaX > TOUCH_SLOP || deltaY > TOUCH_SLOP) {
// 认为是滑动
// 处理滑动逻辑
}
return true;
case MotionEvent.ACTION_UP:
deltaX = Math.abs(event.getX() - mLastX);
deltaY = Math.abs(event.getY() - mLastY);
if (deltaX < TOUCH_SLOP && deltaY < TOUCH_SLOP) {
// 认为是点击
performClick();
}
return true;
}
return super.onTouchEvent(event);
}
5.4 嵌套滚动冲突
通过水平移动的距离,大于Y轴移动距离和最小移动距离,来拦截左右滑动的情况
// 父容器通过方向判断是否拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = ev.getX();
mLastY = ev.getY();
return false;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(ev.getX() - mLastX);
float deltaY = Math.abs(ev.getY() - mLastY);
// 如果是水平方向移动,父容器拦截
if (deltaX > deltaY && deltaX > mTouchSlop) {
return true;
}
return false;
}
return super.onInterceptTouchEvent(ev);
}
5.5 多点触控处理
通过event.getPointerId获取每个手指
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// 处理手指按下
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
// 记录该手指的初始位置
break;
case MotionEvent.ACTION_MOVE:
// 处理所有手指的移动
for (int i = 0; i < event.getPointerCount(); i++) {
pointerId = event.getPointerId(i);
// 处理每个手指的移动
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
// 处理手指抬起
pointerIndex = event.getActionIndex();
pointerId = event.getPointerId(pointerIndex);
// 清理该手指的状态
break;
}
return true;
}
5.6 自定义ViewGroup解决冲突
public class CustomViewGroup extends ViewGroup {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 完全自定义事件分发逻辑
boolean handled = false;
// 实现自己的分发策略
if (shouldInterceptEvent(ev)) {
// 自己处理
handled = onTouchEvent(ev);
} else {
// 找到合适的子View处理
View targetChild = findTargetChild(ev);
if (targetChild != null) {
// 分发给子View
handled = targetChild.dispatchTouchEvent(ev);
}
}
return handled;
}
private boolean shouldInterceptEvent(MotionEvent ev) {
// 自定义拦截逻辑
return false;
}
private View findTargetChild(MotionEvent ev) {
// 自定义查找目标子View的逻辑
return null;
}
}
5.7 手势检测器GestureDetector类辅助处理
public class MyView extends View {
private GestureDetector mGestureDetector;
public MyView(Context context) {
super(context);
initGestureDetector();
}
private void initGestureDetector() {
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
// 处理单击事件
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 处理滑动事件
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 处理快速滑动事件
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// 处理双击事件
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// 处理长按事件
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将事件交给GestureDetector处理
return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
}
GestureDetector的优势:
简化常见手势的识别
封装了时间和距离阈值的计算
支持单击、双击、长按、滑动、快速滑动等多种手势
6. 资料
通过网盘分享的文件:事件分发机制.xmind
链接: https://pan.baidu.com/s/1vnJlyraXW6by0P7vNm-N1A 提取码: 6i6b 复制这段内容后打开百度网盘手机App,操作更方便哦

823

被折叠的 条评论
为什么被折叠?



