Android 的 View 事件分发机制

Android 的 View 事件分发机制,主要指 触摸事件 MotionEvent 从系统传递到 ActivityViewGroupView 的过程。

核心方法有三个:

dispatchTouchEvent(MotionEvent ev)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent ev)

其中:

方法 所属对象 作用
dispatchTouchEvent() Activity / ViewGroup / View 分发事件
onInterceptTouchEvent() ViewGroup 判断是否拦截事件
onTouchEvent() View 处理事件

一、事件分发整体流程

触摸事件大致流程如下:

Activity
  ↓
Window
  ↓
DecorView
  ↓
ViewGroup
  ↓
子 View

通常可以简化理解为:

Activity.dispatchTouchEvent()
        ↓
ViewGroup.dispatchTouchEvent()
        ↓
ViewGroup.onInterceptTouchEvent()
        ↓
View.dispatchTouchEvent()
        ↓
View.onTouchEvent()

二、Activity 的事件分发

当用户触摸屏幕时,事件首先到达 Activity

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

含义是:

  1. 先交给 Window / DecorView 向下分发;
  2. 如果下面的 View 消费了事件,返回 true
  3. 如果没人处理,Activity 自己的 onTouchEvent() 处理。

三、ViewGroup 的事件分发

ViewGroup 比普通 View 多了一个方法:

onInterceptTouchEvent()

它可以决定是否拦截事件。

简化逻辑如下:

public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean intercepted = onInterceptTouchEvent(ev);

    if (!intercepted) {
        // 不拦截,分发给子 View
        child.dispatchTouchEvent(ev);
    } else {
        // 拦截,自己处理
        onTouchEvent(ev);
    }
}

四、View 的事件分发

普通 View 没有子 View,所以它的分发比较简单:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (onTouchListener != null && onTouchListener.onTouch(this, ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

也就是说:

  1. 先执行 OnTouchListener.onTouch()
  2. 如果 onTouch() 返回 true,事件被消费;
  3. 如果返回 false,继续执行 onTouchEvent()
  4. onTouchEvent() 中可能触发 onClick()

五、三个返回值的含义

1. dispatchTouchEvent()

return true;

表示事件被当前对象消费,不再继续传递。

return false;

表示当前对象不处理,交回上层处理。

return super.dispatchTouchEvent(ev);

表示按照系统默认逻辑继续分发。


2. onInterceptTouchEvent()

只存在于 ViewGroup

return true;

表示拦截事件,事件不再传给子 View,由当前 ViewGroup 的 onTouchEvent() 处理。

return false;

表示不拦截,事件继续传给子 View。

return super.onInterceptTouchEvent(ev);

默认一般是不拦截。


3. onTouchEvent()

return true;

表示当前 View 消费该事件。

return false;

表示当前 View 不消费该事件,交给父 View 处理。


六、事件序列

一次完整点击事件通常包含:

ACTION_DOWN
ACTION_MOVE
ACTION_UP

例如:

手指按下 → ACTION_DOWN
手指移动 → ACTION_MOVE
手指抬起 → ACTION_UP

Android 的事件分发是以 事件序列 为单位的。

重要规则

如果一个 View 在 ACTION_DOWN 中返回了 false,那么后续的 ACTION_MOVEACTION_UP 通常不会再分发给它。

例如:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        return false;
    }
    return true;
}

这种情况下,该 View 后续收不到 MOVEUP


七、点击事件 onClick 的触发条件

onClick() 是在 onTouchEvent() 中触发的。

大致条件是:

  1. View 是可点击的;
  2. 收到了 ACTION_DOWN
  3. 收到了 ACTION_UP
  4. 手指没有明显移出 View 范围;
  5. 事件没有被父 View 拦截。

执行顺序通常是:

onTouch()
↓
onTouchEvent()
↓
onClick()

如果 onTouch() 返回 true,则 onClick() 不会执行。

例如:

view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});

这里返回 true,表示事件被 onTouch() 消费,所以不会触发 onClick()

如果返回 false

view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;
    }
});

事件继续交给 onTouchEvent(),如果满足条件,会触发 onClick()


八、ViewGroup 拦截示例

例如自定义 ViewGroup:

public class MyLayout extends ViewGroup {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
}

含义:

ACTION_DOWN 不拦截,子 View 可以收到
ACTION_MOVE 拦截,后续事件交给 MyLayout 处理

如果父 ViewGroup 在事件中途拦截,子 View 会收到一个:

ACTION_CANCEL

表示事件被取消。


九、父子 View 冲突处理

常见场景:

  • ScrollView 中嵌套 RecyclerView
  • 横向滑动 ViewPager 嵌套纵向列表
  • 自定义滑动控件嵌套点击控件

解决方式通常有两种。


方式一:外部拦截法

父 ViewGroup 根据情况决定是否拦截:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            return false;

        case MotionEvent.ACTION_MOVE:
            if (需要父容器处理) {
                return true;
            }
            return false;

        case MotionEvent.ACTION_UP:
            return false;
    }
    return false;
}

特点:

判断逻辑在父 ViewGroup

方式二:内部拦截法

子 View 请求父 View 不要拦截:

getParent().requestDisallowInterceptTouchEvent(true);

示例:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;

        case MotionEvent.ACTION_MOVE:
            if (需要父容器处理) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
    }
    return super.dispatchTouchEvent(ev);
}

注意:

requestDisallowInterceptTouchEvent(true)ACTION_DOWN 通常无效,因为 ACTION_DOWN 一定会经过父 ViewGroup。


十、常见结论

1. 事件从外向内分发

Activity → ViewGroup → View

2. 事件从内向外消费

View → ViewGroup → Activity

3. ViewGroup 可以拦截事件

onInterceptTouchEvent()

4. View 没有拦截方法

普通 View 没有:

onInterceptTouchEvent()

5. onTouch() 优先于 onClick()

顺序是:

dispatchTouchEvent()
↓
onTouch()
↓
onTouchEvent()
↓
onClick()

6. ACTION_DOWN 很关键

如果某个 View 不消费 ACTION_DOWN,通常后续事件不会再给它。


十一、简单记忆口诀

分发 dispatchTouchEvent
拦截 onInterceptTouchEvent
消费 onTouchEvent

Activity 先分发
ViewGroup 可拦截
View 只消费

DOWN 决定归属
MOVE 可能拦截
UP 触发点击

总结一句话:

Android 触摸事件从 Activity 开始向下分发,经过 ViewGroup 判断是否拦截,最终交给目标 View 处理;如果子 View 不处理,事件会向父容器回传,由父 ViewGroup 或 Activity 处理。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇