Android 事件分发机制 - 事件流向详解
事件来源
事件产生流程
markdown
用户触摸屏幕
↓
InputManager(系统服务)
↓
通过Binder传递到应用进程
↓
主线程的MessageQueue
↓
Looper取出事件
↓
Activity.dispatchTouchEvent()
↓
开始事件分发
ViewGroup事件流向详解
ViewGroup的三个方法
| 方法 | 作用 | 返回值 |
|---|---|---|
dispatchTouchEvent() |
分发事件 | boolean |
onInterceptTouchEvent() |
拦截事件 | boolean |
onTouchEvent() |
处理事件 | boolean |
dispatchTouchEvent()返回值详解
返回 true
详细事件流向:
scss
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent()被调用
↓
ViewGroup A.dispatchTouchEvent()返回true ✅
↓
【关键点】返回true表示事件被消费,方法立即返回
↓
不会调用onInterceptTouchEvent()(后续逻辑不执行)
↓
不会遍历子View(不会进入子View遍历逻辑)
↓
不会调用onTouchEvent()(不会调用onTouchEvent())
↓
事件分发结束,ViewGroup A成为目标View
↓
后续ACTION_MOVE、ACTION_UP会继续传递给ViewGroup A
【原因】系统记录ViewGroup A为目标View(mFirstTouchTarget)
最终去向: ✅ 事件被ViewGroup A消费,不再传递
详细说明:
-
为什么不会调用onInterceptTouchEvent()?
- dispatchTouchEvent()返回true后,方法立即返回
- 不会执行后续的拦截判断逻辑
- 这是性能优化,避免不必要的判断
-
为什么不会传递给子View?
- dispatchTouchEvent()返回true表示事件被消费
- 系统认为事件已经处理完成,不需要继续传递
- 这是事件分发的核心机制:一旦消费,停止传递
-
为什么后续事件会继续传递?
- 系统会记录处理ACTION_DOWN的View(mFirstTouchTarget)
- 后续的MOVE、UP会直接传递给这个View
- 这是为了保持事件序列的一致性
代码示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 完全控制事件处理
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理按下事件
handleDown(event);
return true; // ✅ 返回true,事件在此停止
case MotionEvent.ACTION_MOVE:
// 处理移动事件
handleMove(event);
return true;
case MotionEvent.ACTION_UP:
// 处理抬起事件
handleUp(event);
return true;
}
return true; // 所有事件都消费
}
// 注意:onInterceptTouchEvent()和onTouchEvent()不会被调用
}
实际应用场景:
- 自定义ViewGroup需要完全控制所有触摸事件
- 需要阻止事件传递给子View
- 实现特殊的触摸交互逻辑
返回 false
详细事件流向:
scss
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent()被调用
↓
ViewGroup A.dispatchTouchEvent()返回false ❌
↓
【关键点】返回false表示不处理事件,方法立即返回
↓
不会调用onInterceptTouchEvent()(后续逻辑不执行)
↓
不会遍历子View(不会进入子View遍历逻辑)
↓
不会调用onTouchEvent()(不会调用onTouchEvent())
↓
事件返回到父ViewGroup(DecorView)
↓
DecorView会尝试其他子View,或调用自己的onTouchEvent()
↓
如果都不处理,最终调用Activity.onTouchEvent()
↓
后续ACTION_MOVE、ACTION_UP不会传递给ViewGroup A
【原因】系统不会记录ViewGroup A为目标View
最终去向: ⬆️ 事件向上传递给父ViewGroup/Activity
详细说明:
-
为什么不会调用onInterceptTouchEvent()?
- dispatchTouchEvent()返回false后,方法立即返回
- 不会执行后续的拦截判断逻辑
- 表示ViewGroup完全不参与事件处理
-
为什么不会传递给子View?
- dispatchTouchEvent()返回false表示不处理事件
- 系统认为这个ViewGroup不参与事件分发
- 不会进入子View遍历逻辑
-
为什么后续事件不会传递?
- 系统只记录处理ACTION_DOWN的View
- 如果ACTION_DOWN不被处理,不会记录目标View
- 后续MOVE、UP不会传递给该ViewGroup
代码示例:
java
public class DisabledViewGroup extends ViewGroup {
private boolean mEnabled = false;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!mEnabled) {
// ViewGroup被禁用,不处理事件
return false; // ❌ 返回false,事件向上传递
}
return super.dispatchTouchEvent(event);
}
// 注意:onInterceptTouchEvent()和onTouchEvent()不会被调用
}
实际应用场景:
- ViewGroup被禁用时
- ViewGroup不可见时(GONE)
- ViewGroup需要将事件完全交给父View处理时
返回 super
详细事件流向:
scss
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent()被调用
↓
ViewGroup A.dispatchTouchEvent()返回super
↓
【关键点】返回super表示使用系统默认处理逻辑
↓
进入ViewGroup.dispatchTouchEvent()的默认实现
↓
首先调用onInterceptTouchEvent()判断是否拦截
↓
根据onInterceptTouchEvent()的返回值决定:
├─ 如果返回true → 拦截,不传递给子View
│ └─ 调用ViewGroup A.onTouchEvent()
│ ├─ 返回true → ViewGroup A消费 ✅
│ └─ 返回false → 向上传递 ⬆️
└─ 如果返回false → 不拦截,传递给子View
└─ 遍历子View,调用子View.dispatchTouchEvent()
├─ 子View处理 → 子View消费 ✅
└─ 子View不处理 → 调用ViewGroup A.onTouchEvent()
最终去向: 🔄 根据onInterceptTouchEvent()和onTouchEvent()的返回值决定
详细说明:
-
为什么需要调用onInterceptTouchEvent()?
- ViewGroup需要判断是否拦截事件
- 这是ViewGroup的核心功能:控制子View的事件
- 只有ViewGroup有这个方法
-
拦截和不拦截的区别:
- 拦截(true):事件不传递给子View,由ViewGroup自己处理
- 不拦截(false):事件传递给子View,让子View处理
-
子View处理和不处理的区别:
- 子View处理:事件被子View消费,ViewGroup的onTouchEvent()不会被调用
- 子View不处理:事件返回ViewGroup,调用ViewGroup的onTouchEvent()
代码示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 使用系统默认处理逻辑
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 系统会调用这个方法判断是否拦截
// 返回true拦截,返回false不拦截
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果拦截或子View不处理,会调用这个方法
return super.onTouchEvent(event);
}
}
实际应用场景:
- 大多数情况下使用
- 需要系统默认的事件分发逻辑
- 不需要完全自定义事件处理
onInterceptTouchEvent()返回值详解
⚠️ 注意:只有ViewGroup有,View没有此方法
返回 true
详细事件流向:
scss
ViewGroup A.dispatchTouchEvent() → super
↓
调用ViewGroup A.onInterceptTouchEvent()
↓
ViewGroup A.onInterceptTouchEvent()返回true ✅
↓
【关键点】返回true表示拦截事件,不传递给子View
↓
不会遍历子View(拦截了,不需要传递)
↓
不会调用子View的dispatchTouchEvent()(子View不会收到事件)
↓
如果子View之前收到了ACTION_DOWN,会收到ACTION_CANCEL
【说明】DOWN时不拦截,MOVE时拦截的情况
↓
调用ViewGroup A.onTouchEvent()(拦截后,由ViewGroup自己处理)
↓
根据onTouchEvent()的返回值决定:
├─ 返回true → ViewGroup A消费事件 ✅
└─ 返回false → 事件向上传递给父ViewGroup ⬆️
最终去向: 🛑 拦截,不传递给子View,调用自己的onTouchEvent()
详细说明:
-
为什么不会传递给子View?
- onInterceptTouchEvent()返回true表示拦截
- 拦截意味着ViewGroup要自己处理事件
- 系统不会将事件传递给子View
-
ACTION_CANCEL什么时候发送?
- 当ViewGroup在MOVE时拦截,而子View之前收到了DOWN
- 系统会发送ACTION_CANCEL给子View
- 子View应该清理状态,恢复到初始状态
-
拦截的时机:
- 可以在DOWN时拦截
- 可以在MOVE时拦截(更常见)
- 可以在UP时拦截(少见)
代码示例:
java
public class CustomScrollView extends ScrollView {
private float mLastX, mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
// DOWN时不拦截,让子View可以点击
return false; // ❌ 不拦截
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(x - mLastX);
float deltaY = Math.abs(y - mLastY);
// 如果是垂直滑动,拦截事件
if (deltaY > deltaX && deltaY > 10) {
// 此时子View会收到ACTION_CANCEL
return true; // ✅ 拦截
}
return false; // ❌ 不拦截
case MotionEvent.ACTION_UP:
return false; // ❌ 不拦截
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 拦截后,自己处理滑动
handleScroll(event);
return true; // ✅ 消费事件
}
}
实际应用场景:
- ScrollView需要拦截子View的滑动事件
- ViewPager需要拦截子View的左右滑动
- 自定义ViewGroup需要控制子View的事件
返回 false
详细事件流向:
sql
ViewGroup A.dispatchTouchEvent() → super
↓
调用ViewGroup A.onInterceptTouchEvent()
↓
ViewGroup A.onInterceptTouchEvent()返回false ❌
↓
【关键点】返回false表示不拦截,事件继续传递给子View
↓
开始遍历子View(从后往前,上层View优先)
↓
检查子View是否可接收事件(可见、坐标范围内、可用)
↓
对于符合条件的子View,调用子View.dispatchTouchEvent()
↓
根据子View的返回值决定:
├─ 子View返回true → 子View消费事件 ✅
│ └─ 记录目标View(mFirstTouchTarget = 子View)
│ └─ ViewGroup A的onTouchEvent()不会被调用
│ └─ 后续MOVE、UP会直接传递给该子View
└─ 子View返回false → 继续遍历下一个子View
└─ 如果所有子View都不处理
└─ 调用ViewGroup A.onTouchEvent()
├─ 返回true → ViewGroup A消费 ✅
└─ 返回false → 向上传递 ⬆️
最终去向: ⬇️ 不拦截,传递给子View
详细说明:
-
为什么从后往前遍历?
- 后添加的View在Z轴上层
- 上层View应该优先响应触摸事件
- 符合用户的视觉预期
-
子View的检查条件:
- 可见性:必须是VISIBLE(GONE和INVISIBLE不接收事件)
- 坐标:触摸点必须在子View的区域内
- 可用性:子View必须是enabled状态
-
mFirstTouchTarget的作用:
- 记录处理ACTION_DOWN的子View
- 后续MOVE、UP会直接传递给这个View
- 避免重复遍历,提高性能
代码示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 不拦截,让子View正常处理事件
return false; // ❌ 不拦截
}
// 系统会自动遍历子View,调用子View.dispatchTouchEvent()
// 如果子View处理,事件被子View消费
// 如果子View不处理,会调用onTouchEvent()
}
实际应用场景:
- 大多数情况下使用
- 需要子View正常处理事件
- 不需要拦截子View的事件
- 普通的布局容器(LinearLayout、RelativeLayout等)
返回 super
详细事件流向:
scss
ViewGroup A.dispatchTouchEvent() → super
↓
调用ViewGroup A.onInterceptTouchEvent()
↓
ViewGroup A.onInterceptTouchEvent()返回super
↓
【关键点】返回super表示使用系统默认实现
↓
系统默认实现返回false(ViewGroup默认不拦截)
↓
等同于返回false,事件继续传递给子View
最终去向: ⬇️ 等同于返回false,传递给子View
详细说明:
-
为什么默认返回false?
- ViewGroup的设计理念是:默认不拦截,让子View处理
- 只有在需要时才拦截(如ScrollView需要滑动)
- 这样可以保证子View的正常交互
-
super和false的区别:
- 功能上:完全相同,都表示不拦截
- 语义上:super表示使用系统默认,false表示明确不拦截
- 推荐:大多数情况下使用super,更符合面向对象设计
-
什么时候需要返回false?
- 当需要明确表示不拦截时
- 当需要覆盖父类的拦截逻辑时
代码示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 使用系统默认实现(返回false)
return super.onInterceptTouchEvent(event);
}
// 等同于:
// @Override
// public boolean onInterceptTouchEvent(MotionEvent event) {
// return false;
// }
}
实际应用场景:
- 大多数情况下使用
- 不需要自定义拦截逻辑
- 普通的布局容器
onTouchEvent()返回值详解
返回 true
详细事件流向:
css
ViewGroup A.onTouchEvent()被调用
【调用时机】拦截了事件 或 子View不处理事件
↓
ViewGroup A.onTouchEvent()返回true ✅
↓
【关键点】返回true表示消费事件,不再向上传递
↓
事件分发结束,系统记录ViewGroup A为目标View(mFirstTouchTarget)
↓
父ViewGroup/Activity.onTouchEvent()不会被调用
↓
后续ACTION_MOVE、ACTION_UP会继续传递给ViewGroup A
【说明】后续事件会直接调用ViewGroup A.dispatchTouchEvent()
最终去向: ✅ 事件被ViewGroup A消费,不再向上传递
详细说明:
-
什么时候会调用onTouchEvent()?
- 情况1:ViewGroup拦截了事件(onInterceptTouchEvent返回true)
- 情况2:子View不处理事件(子View的onTouchEvent返回false)
- 情况3:没有子View或子View不可见
-
为什么后续事件会继续传递?
- 系统会记录处理ACTION_DOWN的View
- 后续MOVE、UP会直接传递给这个View
- 这是为了保持事件序列的一致性
-
后续事件的拦截判断:
- 后续MOVE、UP仍然会调用onInterceptTouchEvent()
- 如果拦截,子View会收到ACTION_CANCEL
- 这是动态拦截机制
代码示例:
java
public class CustomViewGroup extends ViewGroup {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// 拦截事件
return true; // ✅ 拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理按下
handleDown(event);
return true; // ✅ 消费事件
case MotionEvent.ACTION_MOVE:
// 处理移动
handleMove(event);
return true; // ✅ 消费事件
case MotionEvent.ACTION_UP:
// 处理抬起
handleUp(event);
return true; // ✅ 消费事件
}
return true;
}
}
实际应用场景:
- ScrollView处理滑动事件
- ViewPager处理左右滑动
- 自定义ViewGroup需要处理触摸事件
- 点击空白区域时处理事件
返回 false
详细事件流向:
scss
ViewGroup A.onTouchEvent()被调用
【调用时机】拦截了事件 或 子View不处理事件
↓
ViewGroup A.onTouchEvent()返回false ❌
↓
【关键点】返回false表示不消费事件,向上传递
↓
事件返回到父ViewGroup(DecorView)
↓
DecorView会尝试其他子View,或调用自己的onTouchEvent()
↓
如果都不处理,最终调用Activity.onTouchEvent()
↓
系统不会记录ViewGroup A为目标View
↓
后续ACTION_MOVE、ACTION_UP不会传递给ViewGroup A
【说明】后续事件会重新经过完整的分发流程
最终去向: ⬆️ 事件向上传递给父ViewGroup/Activity
详细说明:
-
为什么后续事件不会传递?
- 系统只记录处理ACTION_DOWN的View
- 如果ACTION_DOWN不被处理,不会记录目标View
- 后续MOVE、UP不会传递给该ViewGroup
-
向上传递的路径:
- ViewGroup A → 父ViewGroup(DecorView)→ Activity
- 每一层都会尝试处理事件
- 如果都不处理,事件会被丢弃
-
什么时候返回false?
- ViewGroup不需要处理事件时
- 需要将事件交给父View处理时
- ViewGroup被禁用时
代码示例:
java
public class CustomViewGroup extends ViewGroup {
private boolean mEnabled = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return true; // 拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mEnabled) {
// ViewGroup被禁用,不处理事件
return false; // ❌ 不消费,向上传递
}
return super.onTouchEvent(event);
}
}
实际应用场景:
- ViewGroup被禁用时
- ViewGroup不需要处理事件时
- 需要将事件交给父View处理时
返回 super
详细事件流向:
ini
ViewGroup A.onTouchEvent()被调用
↓
ViewGroup A.onTouchEvent()返回super
↓
【关键点】返回super表示使用系统默认实现
↓
系统检查ViewGroup的clickable属性(ViewGroup默认clickable=false)
↓
根据clickable属性决定返回值:
├─ clickable=true → 返回true(消费事件)✅
│ └─ 事件被ViewGroup A消费,后续MOVE、UP会继续传递
│ └─ 可以触发点击事件(如果设置了OnClickListener)
└─ clickable=false → 返回false(不消费事件)❌
└─ 事件向上传递,后续MOVE、UP不会传递
└─ 不会触发点击事件
最终去向: 🔄 根据clickable属性决定(ViewGroup默认返回false)
详细说明:
-
ViewGroup的clickable默认值:
- ViewGroup默认clickable=false
- 所以super.onTouchEvent()通常返回false
- 这与View不同(View的Button等默认clickable=true)
-
如何让ViewGroup可点击?
javaviewGroup.setClickable(true); // 或者 viewGroup.setOnClickListener(listener); // 会自动设置clickable=true -
clickable=true时的行为:
- onTouchEvent()返回true
- 事件被消费
- 可以触发点击事件
代码示例:
java
public class CustomViewGroup extends ViewGroup {
public CustomViewGroup(Context context) {
super(context);
// 默认clickable=false
// super.onTouchEvent()会返回false
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 使用系统默认实现
// ViewGroup默认返回false
return super.onTouchEvent(event);
}
// 如果需要处理点击,可以:
public void enableClick() {
setClickable(true); // 设置为可点击
setOnClickListener(v -> {
// 处理点击
});
}
}
实际应用场景:
- 大多数情况下使用
- 不需要自定义触摸处理
- 普通的布局容器
View事件流向详解
View的两个方法
| 方法 | 作用 | 返回值 |
|---|---|---|
dispatchTouchEvent() |
分发事件 | boolean |
onTouchEvent() |
处理事件 | boolean |
⚠️ 注意:View没有onInterceptTouchEvent()方法
dispatchTouchEvent()返回值详解
返回 true
详细事件流向:
css
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false(不拦截)
↓
ViewGroup A遍历子View,找到View B
↓
调用View B.dispatchTouchEvent()
↓
View B.dispatchTouchEvent()返回true ✅
↓
【关键点】返回true表示事件被消费,方法立即返回
↓
不会调用onTouchEvent()(后续逻辑不执行)
↓
ViewGroup A记录View B为目标View(mFirstTouchTarget = View B)
↓
后续ACTION_MOVE、ACTION_UP会继续传递给View B
【说明】后续事件会直接调用View B.dispatchTouchEvent()
最终去向: ✅ 事件被View B消费,不再传递
详细说明:
-
为什么不会调用onTouchEvent()?
- dispatchTouchEvent()返回true后,方法立即返回
- 不会执行后续的onTouchEvent()调用
- 这是性能优化,避免不必要的方法调用
-
为什么后续事件会继续传递?
- 系统会记录处理ACTION_DOWN的View
- 后续MOVE、UP会直接传递给这个View
- 不会再次经过完整的分发流程
-
ViewGroup的拦截对后续事件的影响:
- 如果DOWN时没拦截,后续MOVE、UP也不会拦截(除非动态拦截)
- 如果DOWN时拦截了,后续MOVE、UP会继续拦截
代码示例:
java
public class CustomView extends View {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 完全控制事件处理
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleDown(event);
return true; // ✅ 返回true,事件在此停止
case MotionEvent.ACTION_MOVE:
handleMove(event);
return true;
case MotionEvent.ACTION_UP:
handleUp(event);
return true;
}
return true; // 所有事件都消费
}
// 注意:onTouchEvent()不会被调用
}
实际应用场景:
- 自定义View需要完全控制所有触摸事件
- 需要实现特殊的触摸交互逻辑
- 需要阻止系统默认的点击处理
返回 false
详细事件流向:
css
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false(不拦截)
↓
ViewGroup A遍历子View,找到View B
↓
调用View B.dispatchTouchEvent()
↓
View B.dispatchTouchEvent()返回false ❌
↓
【关键点】返回false表示不处理事件,方法立即返回
↓
不会调用onTouchEvent()(后续逻辑不执行)
↓
事件返回到ViewGroup A
↓
ViewGroup A继续遍历其他子View,或调用自己的onTouchEvent()
↓
系统不会记录View B为目标View
↓
后续ACTION_MOVE、ACTION_UP不会传递给View B
最终去向: ⬆️ 事件向上传递给父ViewGroup
详细说明:
-
为什么不会调用onTouchEvent()?
- dispatchTouchEvent()返回false后,方法立即返回
- 不会执行后续的onTouchEvent()调用
- 表示View完全不参与事件处理
-
ViewGroup会如何处理?
- ViewGroup会继续遍历其他子View
- 如果所有子View都不处理,调用ViewGroup自己的onTouchEvent()
- 这是ViewGroup的默认行为
-
为什么后续事件不会传递?
- 系统只记录处理ACTION_DOWN的View
- 如果ACTION_DOWN不被处理,不会记录目标View
- 后续MOVE、UP不会传递给该View
代码示例:
java
public class DisabledView extends View {
private boolean mEnabled = false;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!mEnabled) {
// View被禁用,不处理事件
return false; // ❌ 返回false,事件向上传递
}
return super.dispatchTouchEvent(event);
}
// 注意:onTouchEvent()不会被调用
}
实际应用场景:
- View被禁用时(enabled=false)
- View不可见时(visibility=GONE)
- View需要将事件完全交给父View处理时
返回 super
详细事件流向:
scss
Activity → Window → DecorView → ViewGroup A
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false(不拦截)
↓
ViewGroup A遍历子View,找到View B
↓
调用View B.dispatchTouchEvent()
↓
View B.dispatchTouchEvent()返回super
↓
【关键点】返回super表示使用系统默认处理逻辑
↓
进入View.dispatchTouchEvent()的默认实现
↓
检查安全性和View是否可用
↓
如果有OnTouchListener,先调用OnTouchListener
【说明】OnTouchListener的优先级高于onTouchEvent()
↓
根据OnTouchListener的返回值决定:
├─ 返回true → 不调用onTouchEvent(),事件被OnTouchListener消费 ✅
└─ 返回false或null → 调用onTouchEvent()
└─ 根据onTouchEvent()的返回值决定:
├─ 返回true → View消费 ✅
└─ 返回false → 向上传递 ⬆️
最终去向: 🔄 根据OnTouchListener和onTouchEvent()的返回值决定
详细说明:
-
OnTouchListener的优先级:
- OnTouchListener在onTouchEvent()之前调用
- 如果OnTouchListener返回true,onTouchEvent()不会被调用
- 这是为了给外部监听器更高的优先级
-
安全检查:
- onFilterTouchEventForSecurity():检查事件是否安全
- 防止某些安全漏洞
-
View的可用性检查:
- 如果View不可用(enabled=false),某些逻辑不会执行
- 但事件仍然会传递
代码示例:
java
public class CustomView extends View {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 使用系统默认处理逻辑
return super.dispatchTouchEvent(event);
}
// 系统会:
// 1. 检查安全性
// 2. 调用OnTouchListener(如果有)
// 3. 调用onTouchEvent()(如果OnTouchListener不处理)
}
实际应用场景:
- 大多数情况下使用
- 需要系统默认的事件处理逻辑
- 不需要完全自定义事件处理
onTouchEvent()返回值详解
返回 true
详细事件流向:
scss
View B.onTouchEvent()被调用
【调用时机】dispatchTouchEvent()返回super 且 OnTouchListener不处理
↓
View B.onTouchEvent()返回true ✅
↓
【关键点】返回true表示消费事件,不再向上传递
↓
事件分发结束,系统记录View B为目标View(mFirstTouchTarget = View B)
↓
父ViewGroup.onTouchEvent()不会被调用
↓
后续ACTION_MOVE、ACTION_UP会继续传递给View B
↓
如果设置了OnClickListener,在ACTION_UP时会触发点击事件(performClick())
最终去向: ✅ 事件被View B消费,不再向上传递
详细说明:
-
什么时候会调用onTouchEvent()?
- dispatchTouchEvent()返回super
- OnTouchListener返回false或null
- 或者OnTouchListener不存在
-
点击事件的触发:
- 在ACTION_UP时,如果View可点击(clickable=true)
- 系统会调用performClick()
- performClick()会触发OnClickListener
-
后续事件的传递:
- 后续MOVE、UP会直接传递给View B
- 不会再次经过完整的分发流程
- 但仍然会调用dispatchTouchEvent()和onTouchEvent()
代码示例:
java
public class CustomButton extends View {
public CustomButton(Context context) {
super(context);
setClickable(true); // 设置为可点击
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下反馈
setBackgroundColor(Color.RED);
return true; // ✅ 消费事件
case MotionEvent.ACTION_MOVE:
// 移动处理
return true; // ✅ 消费事件
case MotionEvent.ACTION_UP:
// 抬起反馈
setBackgroundColor(Color.BLUE);
// 触发点击事件(如果设置了OnClickListener)
performClick();
return true; // ✅ 消费事件
}
return true;
}
}
实际应用场景:
- 自定义View需要处理触摸事件
- 需要实现自定义的触摸反馈
- 需要触发点击事件
- Button、ImageView等可点击的View
返回 false
详细事件流向:
scss
View B.onTouchEvent()被调用
【调用时机】dispatchTouchEvent()返回super 且 OnTouchListener不处理
↓
View B.onTouchEvent()返回false ❌
↓
【关键点】返回false表示不消费事件,向上传递
↓
事件返回到ViewGroup A
↓
ViewGroup A会尝试其他子View,或调用自己的onTouchEvent()
↓
系统不会记录View B为目标View
↓
后续ACTION_MOVE、ACTION_UP不会传递给View B
↓
不会触发点击事件(不会调用performClick())
最终去向: ⬆️ 事件向上传递给父ViewGroup
详细说明:
-
为什么不会触发点击事件?
- performClick()是在onTouchEvent()中调用的
- 如果onTouchEvent()返回false,表示不处理事件
- 不处理事件就不会触发点击
-
ViewGroup会如何处理?
- ViewGroup会继续尝试其他子View
- 如果所有子View都不处理,调用ViewGroup自己的onTouchEvent()
- 这是ViewGroup的默认行为
-
什么时候返回false?
- View不需要处理事件时
- View被禁用时
- View需要将事件交给父View处理时
代码示例:
java
public class DisabledView extends View {
private boolean mEnabled = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mEnabled) {
// View被禁用,不处理事件
return false; // ❌ 不消费,向上传递
}
return super.onTouchEvent(event);
}
}
实际应用场景:
- View被禁用时(enabled=false)
- View不需要处理事件时
- View需要将事件交给父View处理时
返回 super
详细事件流向:
sql
View B.onTouchEvent()被调用
↓
View B.onTouchEvent()返回super
↓
【关键点】返回super表示使用系统默认实现
↓
系统检查View的clickable属性(不同View的默认值不同)
↓
根据clickable属性决定返回值:
├─ clickable=true → 返回true(消费事件)✅
│ └─ 事件被View B消费,后续MOVE、UP会继续传递
│ └─ 在ACTION_UP时调用performClick(),触发OnClickListener
└─ clickable=false → 返回false(不消费事件)❌
└─ 事件向上传递,后续MOVE、UP不会传递
└─ 不会触发点击事件
最终去向: 🔄 根据clickable属性决定
详细说明:
-
不同View的clickable默认值:
- Button:clickable=true(默认可点击)
- TextView:clickable=false(默认不可点击)
- ImageView:clickable=false(默认不可点击)
- 自定义View:clickable=false(默认不可点击)
-
如何让View可点击:
java// 方法1:直接设置 view.setClickable(true); // 方法2:设置监听器(会自动设置clickable=true) view.setOnClickListener(listener); -
clickable=true时的完整流程:
- ACTION_DOWN:返回true,消费事件
- ACTION_MOVE:返回true,消费事件
- ACTION_UP:返回true,消费事件,调用performClick()
代码示例:
java
// Button(默认clickable=true)
public class Button extends TextView {
// Button的默认实现
@Override
public boolean onTouchEvent(MotionEvent event) {
// 系统默认实现
// clickable=true,所以返回true
return super.onTouchEvent(event);
}
}
// TextView(默认clickable=false)
public class TextView extends View {
// TextView的默认实现
@Override
public boolean onTouchEvent(MotionEvent event) {
// 系统默认实现
// clickable=false,所以返回false
return super.onTouchEvent(event);
}
}
实际应用场景:
- 大多数情况下使用
- 需要系统默认的点击处理
- Button、ImageView等系统控件
完整场景示例
场景:Activity → ViewGroup A → ViewGroup B → View C
视图层次:
css
Activity
└─ ViewGroup A (LinearLayout)
└─ ViewGroup B (RelativeLayout)
└─ View C (Button)
场景1:ViewGroup A.dispatchTouchEvent() = true
返回值设置:
- ViewGroup A.dispatchTouchEvent() = true ✅
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → true ✅
↓
【事件在ViewGroup A停止】
↓
不会调用onInterceptTouchEvent()
↓
不会传递给ViewGroup B
↓
不会调用onTouchEvent()
↓
后续MOVE、UP继续传递给ViewGroup A
事件最终到哪里: ✅ ViewGroup A消费,ViewGroup B和View C不会收到事件
场景2:ViewGroup A.dispatchTouchEvent() = false
返回值设置:
- ViewGroup A.dispatchTouchEvent() = false ❌
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → false ❌
↓
【事件向上传递给Activity】
↓
不会调用onInterceptTouchEvent()
↓
不会传递给ViewGroup B
↓
不会调用onTouchEvent()
↓
后续MOVE、UP不会传递给ViewGroup A
↓
Activity.onTouchEvent()会被调用
事件最终到哪里: ⬆️ Activity处理,ViewGroup A、B、C都不会收到事件
场景3:ViewGroup A拦截并处理
返回值设置:
- ViewGroup A.dispatchTouchEvent() = super
- ViewGroup A.onInterceptTouchEvent() = true ✅
- ViewGroup A.onTouchEvent() = true ✅
事件流向:
css
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → true ✅ 【拦截】
↓
【事件被拦截,不传递给ViewGroup B】
↓
ViewGroup B不会收到任何事件
↓
View C不会收到任何事件
↓
ViewGroup A.onTouchEvent() → true ✅ 【处理】
↓
【事件被ViewGroup A消费】
↓
后续MOVE、UP继续传递给ViewGroup A
事件最终到哪里: ✅ ViewGroup A消费,ViewGroup B和View C不会收到事件
场景4:ViewGroup A拦截但不处理
返回值设置:
- ViewGroup A.dispatchTouchEvent() = super
- ViewGroup A.onInterceptTouchEvent() = true ✅
- ViewGroup A.onTouchEvent() = false ❌
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → true ✅ 【拦截】
↓
【事件被拦截,不传递给ViewGroup B】
↓
ViewGroup B不会收到任何事件
↓
View C不会收到任何事件
↓
ViewGroup A.onTouchEvent() → false ❌ 【不处理】
↓
【事件向上传递给Activity】
↓
后续MOVE、UP不会传递给ViewGroup A
↓
Activity.onTouchEvent()会被调用
事件最终到哪里: ⬆️ Activity处理,ViewGroup A、B、C都不会收到后续事件
场景5:ViewGroup A不拦截,ViewGroup B不拦截,View C处理
返回值设置:
- ViewGroup A.dispatchTouchEvent() = super
- ViewGroup A.onInterceptTouchEvent() = false ❌
- ViewGroup B.dispatchTouchEvent() = super
- ViewGroup B.onInterceptTouchEvent() = false ❌
- View C.dispatchTouchEvent() = super
- View C.onTouchEvent() = true ✅
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给ViewGroup B】
↓
ViewGroup B.dispatchTouchEvent() → super
↓
ViewGroup B.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给View C】
↓
View C.dispatchTouchEvent() → super
↓
View C.onTouchEvent() → true ✅ 【处理】
↓
【事件被View C消费】
↓
后续MOVE、UP继续传递给View C
事件最终到哪里: ✅ View C消费,ViewGroup A和B不会调用onTouchEvent()
场景6:ViewGroup A不拦截,ViewGroup B不拦截,View C不处理,ViewGroup B处理
返回值设置:
- ViewGroup A.dispatchTouchEvent() = super
- ViewGroup A.onInterceptTouchEvent() = false ❌
- ViewGroup B.dispatchTouchEvent() = super
- ViewGroup B.onInterceptTouchEvent() = false ❌
- View C.dispatchTouchEvent() = super
- View C.onTouchEvent() = false ❌
- ViewGroup B.onTouchEvent() = true ✅
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给ViewGroup B】
↓
ViewGroup B.dispatchTouchEvent() → super
↓
ViewGroup B.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给View C】
↓
View C.dispatchTouchEvent() → super
↓
View C.onTouchEvent() → false ❌ 【不处理】
↓
【事件返回ViewGroup B】
↓
ViewGroup B.onTouchEvent() → true ✅ 【处理】
↓
【事件被ViewGroup B消费】
↓
后续MOVE、UP继续传递给ViewGroup B
事件最终到哪里: ✅ ViewGroup B消费,View C不处理,ViewGroup A不会调用onTouchEvent()
场景7:ViewGroup A不拦截,ViewGroup B不拦截,都不处理
返回值设置:
- ViewGroup A.dispatchTouchEvent() = super
- ViewGroup A.onInterceptTouchEvent() = false ❌
- ViewGroup B.dispatchTouchEvent() = super
- ViewGroup B.onInterceptTouchEvent() = false ❌
- View C.dispatchTouchEvent() = super
- View C.onTouchEvent() = false ❌
- ViewGroup B.onTouchEvent() = false ❌
- ViewGroup A.onTouchEvent() = false ❌
事件流向:
scss
用户触摸
↓
Activity.dispatchTouchEvent()
↓
ViewGroup A.dispatchTouchEvent() → super
↓
ViewGroup A.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给ViewGroup B】
↓
ViewGroup B.dispatchTouchEvent() → super
↓
ViewGroup B.onInterceptTouchEvent() → false ❌ 【不拦截】
↓
【事件传递给View C】
↓
View C.dispatchTouchEvent() → super
↓
View C.onTouchEvent() → false ❌ 【不处理】
↓
【事件返回ViewGroup B】
↓
ViewGroup B.onTouchEvent() → false ❌ 【不处理】
↓
【事件返回ViewGroup A】
↓
ViewGroup A.onTouchEvent() → false ❌ 【不处理】
↓
【事件向上传递给Activity】
↓
后续MOVE、UP不会传递给任何View
↓
Activity.onTouchEvent()会被调用
事件最终到哪里: ⬆️ Activity处理,所有View都不处理
快速查询表
ViewGroup事件流向快速查询
| dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent | 事件最终到哪里 | 后续事件 |
|---|---|---|---|---|
| true | - | - | ✅ ViewGroup停止 | 继续传递给ViewGroup |
| false | - | - | ⬆️ 向上传递给父View/Activity | 不传递给ViewGroup |
| super | true | true | ✅ ViewGroup消费 | 继续传递给ViewGroup |
| super | true | false | ⬆️ 向上传递给父View/Activity | 不传递给ViewGroup |
| super | false | - | ⬇️ 传递给子View | 由子View决定 |
| super | false | true | ✅ ViewGroup消费(子View不处理时) | 继续传递给ViewGroup |
| super | false | false | ⬆️ 向上传递(都不处理) | 不传递给ViewGroup |
View事件流向快速查询
| dispatchTouchEvent | onTouchEvent | 事件最终到哪里 | 后续事件 |
|---|---|---|---|
| true | - | ✅ View停止 | 继续传递给View |
| false | - | ⬆️ 向上传递给父ViewGroup | 不传递给View |
| super | true | ✅ View消费 | 继续传递给View |
| super | false | ⬆️ 向上传递给父ViewGroup | 不传递给View |
事件流向符号说明
- ✅ 消费/停止:事件被消费,不再传递
- ⬆️ 向上传递:事件向上传递给父View/Activity
- ⬇️ 向下传递:事件向下传递给子View
- 🛑 拦截:拦截事件,不传递给子View
- 🔄 默认处理:按照系统默认逻辑处理
核心记忆要点
1. dispatchTouchEvent()返回值
- true → ✅ 事件在此停止,不再传递
- false → ⬆️ 事件向上传递,不处理
- super → 🔄 进入默认处理流程
2. onInterceptTouchEvent()返回值(只有ViewGroup有)
- true → 🛑 拦截,不传递给子View
- false → ⬇️ 不拦截,传递给子View
- super → ⬇️ 等同于false,传递给子View
3. onTouchEvent()返回值
- true → ✅ 消费事件,不再向上传递
- false → ⬆️ 不消费,向上传递
- super → 🔄 根据clickable属性决定