Files
mkdocs/docs/android面试/系统原理/事件分发机制.md
2026-01-15 11:53:37 +08:00

4.5 KiB
Raw Permalink Blame History

事件分发机制

目录


事件分发流程

分发流程

Activity → ViewGroup → View
    ↓         ↓         ↓
dispatchTouchEvent()
    ↓         ↓         ↓
onInterceptTouchEvent() (仅 ViewGroup)
    ↓         ↓         ↓
onTouchEvent()

关键方法

// 1. dispatchTouchEvent():分发事件
// 2. onInterceptTouchEvent():拦截事件(仅 ViewGroup
// 3. onTouchEvent():处理事件

onTouchEvent

onTouchEvent 方法

// View.onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
    // 1. 处理点击事件
    if (event.getAction() == MotionEvent.ACTION_UP) {
        performClick();
    }
    // 2. 返回 true 表示消费事件
    return true;
}

返回值

// true消费事件不再向下传递
// false不消费事件继续向下传递

onInterceptTouchEvent

onInterceptTouchEvent 方法

// ViewGroup.onInterceptTouchEvent()
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 返回 true拦截事件不传递给子 View
    // 返回 false不拦截传递给子 View
    return false;
}

使用场景

// 例如ScrollView 拦截滑动事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_MOVE) {
        // 判断是否需要拦截
        if (needIntercept()) {
            return true; // 拦截,自己处理
        }
    }
    return false; // 不拦截,传递给子 View
}

事件冲突处理

冲突场景

// 1. ScrollView 和 ViewPager 滑动冲突
// 2. ListView 和 Button 点击冲突
// 3. 嵌套滑动冲突

解决方法

方法1外部拦截

// 在父 View 的 onInterceptTouchEvent() 中处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false; // 不拦截 DOWN
            break;
        case MotionEvent.ACTION_MOVE:
            if (needIntercept()) {
                intercepted = true; // 拦截 MOVE
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false; // 不拦截 UP
            break;
    }
    return intercepted;
}

方法2内部拦截

// 在子 View 的 dispatchTouchEvent() 中处理
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (needParentHandle()) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
    }
    return super.dispatchTouchEvent(event);
}

事件分发源码分析

dispatchTouchEvent

// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 1. 检查是否拦截
    boolean intercepted = onInterceptTouchEvent(ev);
    
    if (!intercepted) {
        // 2. 不拦截,分发给子 View
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (child.dispatchTouchEvent(ev)) {
                return true; // 子 View 消费了事件
            }
        }
    }
    
    // 3. 子 View 不消费,自己处理
    return onTouchEvent(ev);
}

面试常见问题

Q1: 事件分发流程?

答案: Activity → ViewGroup → View

  • dispatchTouchEvent():分发事件
  • onInterceptTouchEvent():拦截事件(仅 ViewGroup
  • onTouchEvent():处理事件

Q2: onInterceptTouchEvent 的作用?

答案:

  • 决定是否拦截事件
  • 返回 true拦截不传递给子 View
  • 返回 false不拦截传递给子 View

Q3: 如何解决滑动冲突?

答案:

  1. 外部拦截:在父 View 的 onInterceptTouchEvent() 中处理
  2. 内部拦截:在子 View 的 dispatchTouchEvent() 中处理

Q4: 事件分发的返回值?

答案:

  • true:消费事件,不再传递
  • false:不消费事件,继续传递

最后更新2024年