Android 事件分发机制 - 事件流向详解

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消费,不再传递

详细说明:

  1. 为什么不会调用onInterceptTouchEvent()?

    • dispatchTouchEvent()返回true后,方法立即返回
    • 不会执行后续的拦截判断逻辑
    • 这是性能优化,避免不必要的判断
  2. 为什么不会传递给子View?

    • dispatchTouchEvent()返回true表示事件被消费
    • 系统认为事件已经处理完成,不需要继续传递
    • 这是事件分发的核心机制:一旦消费,停止传递
  3. 为什么后续事件会继续传递?

    • 系统会记录处理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

详细说明:

  1. 为什么不会调用onInterceptTouchEvent()?

    • dispatchTouchEvent()返回false后,方法立即返回
    • 不会执行后续的拦截判断逻辑
    • 表示ViewGroup完全不参与事件处理
  2. 为什么不会传递给子View?

    • dispatchTouchEvent()返回false表示不处理事件
    • 系统认为这个ViewGroup不参与事件分发
    • 不会进入子View遍历逻辑
  3. 为什么后续事件不会传递?

    • 系统只记录处理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()的返回值决定

详细说明:

  1. 为什么需要调用onInterceptTouchEvent()?

    • ViewGroup需要判断是否拦截事件
    • 这是ViewGroup的核心功能:控制子View的事件
    • 只有ViewGroup有这个方法
  2. 拦截和不拦截的区别:

    • 拦截(true):事件不传递给子View,由ViewGroup自己处理
    • 不拦截(false):事件传递给子View,让子View处理
  3. 子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()

详细说明:

  1. 为什么不会传递给子View?

    • onInterceptTouchEvent()返回true表示拦截
    • 拦截意味着ViewGroup要自己处理事件
    • 系统不会将事件传递给子View
  2. ACTION_CANCEL什么时候发送?

    • 当ViewGroup在MOVE时拦截,而子View之前收到了DOWN
    • 系统会发送ACTION_CANCEL给子View
    • 子View应该清理状态,恢复到初始状态
  3. 拦截的时机:

    • 可以在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

详细说明:

  1. 为什么从后往前遍历?

    • 后添加的View在Z轴上层
    • 上层View应该优先响应触摸事件
    • 符合用户的视觉预期
  2. 子View的检查条件:

    • 可见性:必须是VISIBLE(GONE和INVISIBLE不接收事件)
    • 坐标:触摸点必须在子View的区域内
    • 可用性:子View必须是enabled状态
  3. 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

详细说明:

  1. 为什么默认返回false?

    • ViewGroup的设计理念是:默认不拦截,让子View处理
    • 只有在需要时才拦截(如ScrollView需要滑动)
    • 这样可以保证子View的正常交互
  2. super和false的区别:

    • 功能上:完全相同,都表示不拦截
    • 语义上:super表示使用系统默认,false表示明确不拦截
    • 推荐:大多数情况下使用super,更符合面向对象设计
  3. 什么时候需要返回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消费,不再向上传递

详细说明:

  1. 什么时候会调用onTouchEvent()?

    • 情况1:ViewGroup拦截了事件(onInterceptTouchEvent返回true)
    • 情况2:子View不处理事件(子View的onTouchEvent返回false)
    • 情况3:没有子View或子View不可见
  2. 为什么后续事件会继续传递?

    • 系统会记录处理ACTION_DOWN的View
    • 后续MOVE、UP会直接传递给这个View
    • 这是为了保持事件序列的一致性
  3. 后续事件的拦截判断:

    • 后续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

详细说明:

  1. 为什么后续事件不会传递?

    • 系统只记录处理ACTION_DOWN的View
    • 如果ACTION_DOWN不被处理,不会记录目标View
    • 后续MOVE、UP不会传递给该ViewGroup
  2. 向上传递的路径:

    • ViewGroup A → 父ViewGroup(DecorView)→ Activity
    • 每一层都会尝试处理事件
    • 如果都不处理,事件会被丢弃
  3. 什么时候返回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)

详细说明:

  1. ViewGroup的clickable默认值:

    • ViewGroup默认clickable=false
    • 所以super.onTouchEvent()通常返回false
    • 这与View不同(View的Button等默认clickable=true)
  2. 如何让ViewGroup可点击?

    java 复制代码
    viewGroup.setClickable(true);
    // 或者
    viewGroup.setOnClickListener(listener);  // 会自动设置clickable=true
  3. 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消费,不再传递

详细说明:

  1. 为什么不会调用onTouchEvent()?

    • dispatchTouchEvent()返回true后,方法立即返回
    • 不会执行后续的onTouchEvent()调用
    • 这是性能优化,避免不必要的方法调用
  2. 为什么后续事件会继续传递?

    • 系统会记录处理ACTION_DOWN的View
    • 后续MOVE、UP会直接传递给这个View
    • 不会再次经过完整的分发流程
  3. 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

详细说明:

  1. 为什么不会调用onTouchEvent()?

    • dispatchTouchEvent()返回false后,方法立即返回
    • 不会执行后续的onTouchEvent()调用
    • 表示View完全不参与事件处理
  2. ViewGroup会如何处理?

    • ViewGroup会继续遍历其他子View
    • 如果所有子View都不处理,调用ViewGroup自己的onTouchEvent()
    • 这是ViewGroup的默认行为
  3. 为什么后续事件不会传递?

    • 系统只记录处理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()的返回值决定

详细说明:

  1. OnTouchListener的优先级:

    • OnTouchListener在onTouchEvent()之前调用
    • 如果OnTouchListener返回true,onTouchEvent()不会被调用
    • 这是为了给外部监听器更高的优先级
  2. 安全检查:

    • onFilterTouchEventForSecurity():检查事件是否安全
    • 防止某些安全漏洞
  3. 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消费,不再向上传递

详细说明:

  1. 什么时候会调用onTouchEvent()?

    • dispatchTouchEvent()返回super
    • OnTouchListener返回false或null
    • 或者OnTouchListener不存在
  2. 点击事件的触发:

    • 在ACTION_UP时,如果View可点击(clickable=true)
    • 系统会调用performClick()
    • performClick()会触发OnClickListener
  3. 后续事件的传递:

    • 后续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

详细说明:

  1. 为什么不会触发点击事件?

    • performClick()是在onTouchEvent()中调用的
    • 如果onTouchEvent()返回false,表示不处理事件
    • 不处理事件就不会触发点击
  2. ViewGroup会如何处理?

    • ViewGroup会继续尝试其他子View
    • 如果所有子View都不处理,调用ViewGroup自己的onTouchEvent()
    • 这是ViewGroup的默认行为
  3. 什么时候返回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属性决定

详细说明:

  1. 不同View的clickable默认值:

    • Button:clickable=true(默认可点击)
    • TextView:clickable=false(默认不可点击)
    • ImageView:clickable=false(默认不可点击)
    • 自定义View:clickable=false(默认不可点击)
  2. 如何让View可点击:

    java 复制代码
    // 方法1:直接设置
    view.setClickable(true);
    
    // 方法2:设置监听器(会自动设置clickable=true)
    view.setOnClickListener(listener);
  3. 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属性决定

相关推荐
musashi15 小时前
用 Electron 写了一个 macOS 版本的 wallpaper(附源码、下载地址)
前端·vue.js·electron
满天星辰15 小时前
Typescript之类型总结大全
前端·typescript
JFChen15 小时前
Web 仔用 Node 像 Java 一样写后端服务
前端
XiaoSong15 小时前
React useState 原理和异步更新
前端·react.js
徐徐子15 小时前
从vue3 watch开始理解Vue的响应式原理
前端·vue.js
眯眼因为很困啦15 小时前
GitHub Fork 协作完整流程
前端·git·前端工程化
whisper15 小时前
🚀 React Router 7 + Vercel 部署全指南
前端
还债大湿兄16 小时前
huggingface.co 下载有些要给权限的模型 小记录
开发语言·前端·javascript
叶落无痕5216 小时前
Electron应用自动化测试实例
前端·javascript·功能测试·测试工具·electron·单元测试