Android之事件分发机制

文章中出现的源码均基于8.0

一、基本认识

1. 事件分发的本质

点击事件(MotionEvent)传递到某个具体的View处理的整个过程

2. 事件分发的对象

点击事件(Touch事件):当用户触摸屏幕时,将产生点击事件

事件类型

类型 说明
MotionEvent.ACTION_DOWN 手指刚接触屏幕,一般为事件的开始
MotionEvent.ACTION_MOVE 手指在屏幕移动,在移动的过程中会产生多个move事件
MotionEvent.ACTION_UP 手指从屏幕上松开的一瞬间
MotionEvent.ACTION_CANCEL 结束事件,非人为原因

同一个事件序列:指从手指刚接触屏幕,到手指离开屏幕的那一刻结束,在这一过程产生的一系列事件,这个序列一般以down事件开始,中间含有多个move事件,最终以up事件结束

3. 事件分发的顺序

事件传递的顺序:Activity->Window->DecorView->ViewGroup->View。一个点击事件发生后,总是先传递给当前的Activity,然后通过Window传给DecorView再传给ViewGroup,最终传到View。

在《开发艺术探索》中的事件分发的顺序是:Activity->Window->View,而有的博客上的顺序是:Activity->ViewGroup->View,不过其实两者是一样的(下列会从源码进行分析)。Window是抽象类,其唯一实现类为PhoneWindow,PhoneWindow将事件直接传递给DecorView,而DecorView继承FrameLayout,FrameLayout又是ViewGroup的子类,所以兜兜转转最后也可以认为Window事件分发的实现其实是ViewGroup来实现的。所以也可以认为事件传递的顺序是:Activity->ViewGroup->View。

二、核心方法

View的事件分发机制主要由事件分发->事件拦截->事件响应三步来进行逻辑控制,很巧的这三步刚好对应了三个核心方法

1. 事件分发:dispatchTouchEvent

用来进行事件的分发,如果事件能够传递给当前View,则该方法一定会被调用。返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent的影响,表示是否消耗当前事件。

原型:public boolean dispatchTouchEvent(MotionEvent ev)

return:

  • ture:当前View消耗所有事件
  • false:停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,则事件将被系统消费,处理

2. 事件拦截:onInterceptTouchEvent

需注意的是在Activity,ViewGroup,View中只有ViewGroup有这个方法。故一旦有点击事件传递给View,则View的onTouchEvent方法就会被调用

在dispatchTouch Event内部使用,用来判断是否拦截事件。如果当前View拦截了某个事件,那么该事件序列的其它方法也由当前View处理,故该方法不会被再次调用,因为已经无须询问它是否要拦截该事件。

原型:public boolean onInterceptTouchEvent(MotionEvent ev)

return:

  • ture:对事件拦截,交给本层的onTouchEvent进行处理
  • false:不拦截,分发到子View,由子View的dispatchTouchEvent进行处理
  • super.onInterceptTouchEvent(ev):默认不拦截

3. 事件响应:onTouchEvent

在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再接受到剩下的事件,并且事件将重新交给它的父元素处理,即父元素的onTouchEvent会被调用

原型:public boolean onTouchEvent(MotionEvent ev)

return:

  • true:表示onTouchEvent处理后消耗了当前事件
  • false:不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费,如果到最顶层View还是返回false,则该事件不消费,将交由Activity的onTouchEvent处理。
  • super.onTouchEvent(ev):默认消耗当前事件,与返回true一致。

三、事件分发机制

在分析事件分发机制时,应该从事件分发的顺序入手一步一步解剖。从上文我们知道事件分发顺序为:Activity->Window->DecorView->ViewGroup->View。由于Window与DecorView可以看作是Activity->ViewGroup的过程,故这里将从三部分通过源码来分析事件分发机制:

  • Activity对点击事件的分发机制
  • ViewGroup对点击事件的分发机制
  • View对点击事件的分发机制

1. Activity事件的分发机制

我们知道,当一个点击事件发生时,事件总是最先传递到当前Activity中,由Activity的dispatchTouchEvent来进行事件分发。而Activity会将事件传递给Window对象来分发,Window对象再传递给DecorView。下面将进行源码分析来验证这个过程:

源码:Activity#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent ev) {
   
   
    	//点击事件的开始一般为按下事件,所以总是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   
   
            onUserInteraction();
			
        }
		//如果Activity所属Window的dispatchTouchEvent返回了ture
		//则Activity.dispatchTouchEvent返回ture,点击事件停止往下传递
        if (getWindow().superDispatchTouchEvent(ev)) {
   
   
            return true;
        }
		//如果Window的dispatchTouchEvent返回了false,则点击事件传递给Activity.onTouchEvent
        return onTouchEvent(ev);
    }

上面代码为Activity中的dispatchTouchEvent的源码,通过源码我们可以知道当点击事件发生时,首先会执行onUserInteraction();这个方法又是什么呢?不急,我们跟踪下去。

	//空方法,当该Activity在栈顶时,触屏点击home,back,menu会触发此方法
    public void onUserInteraction() {
   
   
    }

从源码中可以看出在Activity中该方法为空方法,当该Activity在栈顶时,触屏点击home,back,menu会触发此方法,所以这个方法可以实现屏保功能。让我们回到Activity中的dispatchTouchEvent方法中,接着调用了getWindow().superDispatchTouchEvent(ev)方法将事件交给Activity所附属的Window进行分发,如果最终事件被消耗了,则返回true,如果事件没人处理,则Activity调用在自己的onTouchEvent()方法来处理事件。

getWindow是一个Window对象,在Window源码中我们可以发现其实Window就是一个抽象类,显而易见其方法自然是抽象方法,所以我们必须找出其具体实现类。

源码:Window#superDispatchTouchEvent

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
   
   
    ...
     //抽象方法
    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    ...
}

从源码中我们可以发现在Window类前面的注释中是有解释的,这时候就要考验偶们的英语能力辽!其实总体上来说还是挺容易理解的,其实我们只要关注后面一部分的注释就行。

The only existing implementation of this abstract class isandroid.view.PhoneWindow, which you should instantiate when needing a Window.

从这里我们可以知道它的唯一实现类就是PhoneWindow,废话不多说,我们直接看看PhoneWindow中superDispatchTouchEvent方法的实现是如何的呢?

源码:PhoneWindow#superDispatchTouchEvent

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

从源码中可以发现PhoneWindow将事件直接传递给了DecorView,而这个DecorView又是何方圣神呢?如下所示

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
   
       
	......
	public boolean superDispatchTouchEvent(MotionEvent event) {
   
   
        return super.dispatchTouchEvent(event);
    }
    ......
}

@RemoteView
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值