三个维度,不太一样的View事件分发机制(源码解读)

本文是关于Android View事件分发机制的源码解读篇,探讨了事件分发顺序、监听器执行顺序、禁用状态点击事件、onTouch与onTouchEvent的区别等问题,并详细分析了ViewGroup的事件拦截和子View如何阻止父元素拦截事件,以及滑动冲突的原因。通过源码解读,有助于读者全面理解Android View事件分发机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


每一个Android开发者,都绕不开View的事件分发,理解好View的事件分发机制,有利于我们解决各种与设备触摸交互的问题,同时也利于我们实现更复杂、炫酷的自定义View效果。


关于Android View的事件分发机制,笔者打算分为三篇文章来描述,分别为:

(1)Android View事件分发机制之概念理论篇

(2)Android View事件分发机制之源码解读篇

(3)Android View事件分发机制之滑动冲突实战解决篇


本文是第二篇:Android View事件分发机制之源码解读篇


在开始解读源码前,我们先确定好我们的目标,即我们想通过解读源码而理解、明白什么问题:

(1)如果一个View同时监听onClickListener和onTouchListener ,哪个会先被执行呢?

(2)如果设置了onClickListener,onClick一定会被执行吗?

(3)如果一个View是disable状态,是否还会响应点击事件?clickable为false呢?

(4)onTouch()和onTouchEvent()有什么区别?如何去区分呢?

(5)为什么说事件是由外到内的分发,而由内到外的响应呢?

(6)子View能否阻止父对事件的拦截?如果能,该如何处理?

(7)为什么会出现滑动冲突?什么情况下会出现滑动冲突?

(8)在什么情况下会出现ACTION_CANCEL事件?

(9)requestDisallowInterceptTouchEvent能完全阻止父元素拦截事件吗?


我们接下来就带着这9个问题,去仔细解读一下源码,如果从源码层次理解了这几个问题,那么对View的事件分发机制就掌握得比较通透了。


既然事件分发是从dispatchTouchEvent开始,我们就直接从dispatchTouchEvent为切入点吧。


在上篇:View事件分发机制之概念理论篇 我们讲到,事件分发的一个顺序是:

Activity -> Window -> ViewGroup -> ViewGroup -> … (n个ViewGroup ) -> View


那么我们就直接从Activity的dispatchTouchEvent开始,下面一张图是笔者触发一个ImageView的onTouch方法时的调用栈,从这个调用栈也可以看到,事件分先从Activity开始分发,然后再一层一层往下传:

onTouch调用栈

Activity#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
   
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   
        onUserInteraction();
    }
    // 在这里调用了window的dispatchTouchEvent
    if (getWindow().superDispatchTouchEvent(ev)) {
   
        return true;
    }
    return onTouchEvent(ev);
}

getWindow()是哪个呢?直接看源码:

public Window getWindow() {
   
    return mWindow;
}

// mWindow在Activity的attach方法实例化了

final void attach() {
   
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
	
    // 省略其他代码
}

所以,我们直接看PhoneWindow的superDispatchTouchEvent方法

PhoneWindow#superDispatchTouchEvent

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
   
    return mDecor.superDispatchTouchEvent(event);
}

代码继续告诉我们应该找mDecor的回调,这个mDecor就我们我们前文提到的DecorView了:

PhoneWindow

private DecorView mDecor;

一步一步的调用,最终来到了ViewGroup的dispatchTouchEvent。

由于ViewGroup的dispatchTouchEvent的代码比较长,我们截取关键的部分来看:


ViewGroup#dispatchTouchEvent#代码片段1

检查事件是否拦截

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
   
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
    || mFirstTouchTarget != null) {
   
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
   
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
   
        intercepted = false;
    }
} else {
   
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

我们来简单分析一下ViewGroup处理事件拦截的逻辑:

先明确一件事,通过源码能知道,在整个ViewGroup类中,就只有这里调用到了onInterceptTouchEvent,然后我们来看,onInterceptTouchEvent的触发条件有两层:

(1)actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null

(2)(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 结果为false时,即mGroupFlags 不包含FLAG_DISALLOW_INTERCEPT这个标志位时(这里为何说包含而不说等于,可看笔者的另外一篇关于Android中的位运算的文章)


对于第一层,当事件是ACTION_DOWN时,会进入第二层判断;或者当mFirstTouchTarget不为null时,也进入第二层判断。这里可以先简单说明一下,mFirstTouchTarget是后续的处理中,如果ViewGroup有子元素对事件进行消耗处理了,mFirstTouchTarget就会被赋值为那个处理的子元素,这时,mFirstTouchTarget就不会为null。


进入第二层之后,会通过一个位运算(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0判断一下当前View

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yeqiu1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值