Android View 事件分发机制源码详解(ViewGroup篇)

前言

我们在学习View的时候,不可避免会遇到事件的分发,而往往遇到的很多滑动冲突的问题都是由于处理事件分发时不恰当所造成的。因此,深入了解View事件分发机制的原理,对于我们来说是很有必要的。由于View事件分发机制是一个比较复杂的机制,因此笔者将写成两篇文章来详细讲述,分别是ViewGroup和View。因为我们平时所接触的View都不是单一的View,往往是由若干个ViewGroup组合而成,而事件的分发又是由ViewGroup传递到它的子View的,所以我们先从ViewGroup的事件分发说起。注意,以下源码取自安卓5.0(API 21)。

三个重要方法

public boolean dispatchTouchEvent(MotionEvent ev)

该方法用来进行事件的分发,即无论ViewGroup或者View的事件,都是从这个方法开始的。

public boolean onInterceptTouchEvent(MotionEvent ev)

在上一个方法内部调用,表示是否拦截当前事件,返回true表示拦截,如果拦截了事件,那么将不会分发给子View。比如说:ViewGroup拦截了这个事件,那么所有事件都由该ViewGroup处理,它内部的子View将不会获得事件的传递。(但是ViewGroup是默认不拦截事件的,这个下面会解释。)注意:View是没有这个方法的,也即是说,继承自View的一个子View不能重写该方法,也无需拦截事件,因为它下面没有View了,它要么处理事件要么不处理事件,所以最底层的子View不能拦截事件。

public boolean onTouchEvent(MotionEvent ev)

这个方法表示对事件进行处理,在dispatchTouchEvent方法内部调用,如果返回true表示消耗当前事件,如果返回false表示不消耗当前事件。

以上三个方法非常重要,贯穿整个View事件分发的流程,它们的关系可以用如下伪代码呈现:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean handle = false;
    if(onInterceptTouchEvent(ev)){
        handle = onTouchEvent(ev);
    }else{
        handle = child.dispatchTouchEvent(ev);
    }
    return handle;
}

由以上伪代码可得出如下结论:如果一个事件传递到了ViewGroup处,首先会判断当前ViewGroup是否要拦截事件,即调用onInterceptTouchEvent()方法;如果返回true,则表示ViewGroup拦截事件,那么ViewGroup就会调用自身的onTouchEvent来处理事件;如果返回false,表示ViewGroup不拦截事件,此时事件会分发到它的子View处,即调用子View的dispatchTouchEvent方法,如此反复直到事件被消耗掉。
接下来,我们将从源码的角度来分析整个ViewGroup事件分发的流程是怎样的。

从Activity到根ViewGroup

我们知道,事件产生于用户按下屏幕的一瞬间,事件生成后,经过一系列的过程来到我们的Activity层,那么事件是怎样从Activity传递到根ViewGroup的呢?由于这个问题不在本文的讨论范围,所以这里简单提一下:事件到达Activity时,会调用Activity#dispatchTouchEvent方法,在这个方法,会把事件传递给Window,然后Window把事件传递给DecorView,而DecorView是什么呢?它其实是一个根View,即根布局,我们所设置的布局是它的一个子View。最后再从DecorView传递给我们的根ViewGroup。
所以在Activity传递事件给ViwGroup的流程是这样的:Activity->Window->DecorView->ViewGroup

ViewGroup事件分发源码解析

接下来便是本文的重要,对ViewGroup#dispatchTouchEvent()方法源码进行解读,由于源码比较长,所以这里分段贴出。

1、对ACTION_DOWN事件初始化

首先看如下所示的源码:

    ...
    // 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.
          //这里把mFirstTouchTarget设置为null
          cancelAndClearTouchTargets(ev);
          resetTouchState();
      }

首先这里先判断事件是否为DOWN事件,如果是,则初始化,把mFirstTouchTarget置为null。由于一个完整的事件序列是以DOWN开始,以UP结束,所以如果是DOWN事件,那么说明是一个新的事件序列,所以需要初始化之前的状态。这里的mFirstTouchTarget非常重要,后面会说到当ViewGroup的子元素成功处理事件的时候,mFirstTouchTarget会指向子元素,这里要留意一下。

2、检查ViewGroup是否要拦截事件

接着我们往下看:

// Check for interception.
    final
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值