前言
android事件分发算是自定义View不可缺失的一部分,事件分发是指那一类跟屏幕交互的操作等事件,例如滑动,点击,长按这类。这些事件都是由摁下、移动、抬起等基本事件组成的。那事件分发是指当你点击了屏幕,这个事件是如何从Activity传递到真正处理这个事件的View上的过程。例如,我们在做ListView跟ViewPager嵌套的时候,既能左右滑动,又能上下滑动,这些事件是如何避免彼此间的冲突的。下面我会分三节来介绍这一原理。
首先,这次的源码分析是基于25.0.3版本进行的。
分发事件
分发的事件主要是MotionEvent这个类所表示的点击、移动、抬起、取消等事件
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_UP
MotionEvent.ACTION_CANCEL
…
分发的对象
分发的对象是指收到上述事件的类
Activity
ViewGroup
View
上述三个类是主要的事件分发对象,后期的讨论也是集中在这三个类里面。这里有个点得提一下就是ViewGroup是View的子类。这些事件会在这三者的dispatchTouchEvent、onTouchEvent这两个方法里面传递,还有一个ViewGroup特有的onInterceptTouchEvent方法。下面给出以上提到的类以及方法之间的关系。
从这张图,我们可以看出,不同返回值,事件的分发方向不同,这里不做详细分析,这张图是否画得出来作为你对这次源码阅读的成绩。
Demo
Activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
CusViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent"+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
View
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.coffeetime.cusviewdemo.CusLineaLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.coffeetime.cusviewdemo.MainActivity">
<com.example.coffeetime.cusviewdemo.CusView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"/>
</com.example.coffeetime.cusviewdemo.CusLineaLayout>
结果
ACTION_DOWN的值为0
ACTION_MOVE的值为1
从输出的结果的第一行可以看出来最先获取事件的是Activity这一层,从倒数第三行可以看出,ACTION_DOWN这个事件最终消费是在Activity的onTouchEvent这个方法被消费。这个结果跟上面的图片是一致的。
再看看输出结果的倒数两行,ACTION_MOVE事件从dispatchTouchEvent直接就传给了同级的onTouchEvent方法去了,说明ACTION_DOWN事件在哪里被消费了,后续事件也在那里消费,并且跳过中间传递。这个原因会在分析源码的时候给出解释。
Activity
从上述的结果,我们可以得出事件最先被拦截的地方是从Activity的dispatchTouchEvent方法开始,那我们的源码攻略也从这里开始。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这个方法首先判断事件类型,如果是ACTION_DOWN事件,则先执行onUserInteraction()方法;
public void onUserInteraction() {
}
这个方法没有实现,根据文档注释,当有任意一个按键、触屏或者轨迹球事件发生时,栈顶Activity的onUserInteraction会被触发。如果我们需要知道用户是不是正在和设备交互,可以在子类中重写这个方法,去获取通知(比如取消屏保这个场景)。跟这个方法配对的还有onUserLeaveHint方法,这个方法是在用户离开设备的时候触发的。
我们接着回到刚刚那个地方,判断完事件ACTION_DOWN事件之后,会执行getWindow().superDispatchTouchEvent(ev)这个方法,这个getWindow获取的是哪个Window呢,我们进去看看。
public Window getWindow() {
return mWindow;
}
/**
* 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 {
通过这块注释,我们可以看出,window的实现类是phoneWindow。其实在Android里面很多都是可以通过阅读注释或者是官方文档去找出具体的实现类,不是只能依靠百度;好了,那我们可以直接进到phoneWindow类去看下superDispatchTouchEvent这个方法做了些什么
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这个方法又是调用mDecor的同名方法去实现,mDecor又是什么
phoneWindow
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView 是window的顶级View,而DecorView是继承FrameLayout的布局,接着去FragmeLayout里面寻找dispatchTouchEvent方法,而FragmeLayout并没有实现这个方法,也即这个是直接交给ViewGroup去处理的。
小结
在Activity层事件的传递过程如下图
从Activity的dispatchTouchEvent获取事件经过PhoneWindow、DecorView再到最终的ViewGroup,这一层代码比较简单,没有比较难分析的,只是经过的类比较多,其实只要把握的主线,分析起来还是比较简单的。这一篇比较少,下一篇是ViewGroup的分析,由于ViewGroup的源码比较多,所以才拆开来写,不然太长了。