Android view 的事件分发机制

时间:2024-01-20 21:05:15

1 事件的传递顺序是 Activity -> Window -> 顶层View

touch 事件产生后,最先由 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);
}

接着事件会传到 Window 的 superDispatchTouchEvent。 如果所有的 view 都没有消费事件,最后会交给 activity 的 onTouchEvent 处理。

2 Window 是一个抽象类,它的唯一实现类是 PhoneWindow

/**
* 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 {
}

查看 PhoneWindow 的 superDispatchTouchEvent

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

调用了 mDecor 的 superDispatchTouchEvent

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

mDecor 就是 DecorView, DecorView 是一个 FrameLayout, 就是我们 setContentView 所设置 view 的父容器。

继续看 DecorView 的superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

调用了父类的 dispatchTouchEvent,  FrameLayout 中没有 dispatchTouchEvent, 实际上调用了 FrameLayout 的父类 ViewGroup 的 dispatchTouchEvent。

到此事件就传递到了顶层View(ViewGroup) 中,接着事件就从顶层 view 开始向子view分发。

3 事件分发主要涉及 3 个方法

public boolean dispatchTouchEvent(MotionEvent event)

事件传递到一个 view 时就会先调用这个 view 的 dispatchTouchEvent 进行往下分发

public boolean onInterceptTouchEvent(MotionEvent ev)

进行事件的拦截,只有 ViewGroup 有拦截方法, 单一View没有,事件传到 单一View 就直接调用 onTouchEvent 进行处理了

public boolean onTouchEvent(MotionEvent event)

处理事件,true 表示消费这个事件, false 表示不消费

4 下面的伪代码可以很好的说明事件的分发过程

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

事件传递到顶层 ViewGroup 中后, 就会调用 ViewGroup dispatchTouchEvent 进行分发。如果这个 ViewGroup 的 onInterceptTouchEvent 返回 true 表示它要拦截这个事件,接着就会调用它的 onTouchEvent 进行处理。如果不拦截则会交给它的子 view 继续进行分发, 如此反复直到事件被最终处理。

正常情况下,一个事件序列只能被一个 view 拦截且消耗。一个 view 一旦拦截了某个事件,那么同一个事件序列内的所有的事件都会交给它处理。

如果一个 view 的 onTouchEvent 返回 false, 那么它的父容器的 onTouchEvent 将会被调用,依次类推。如果所有的 view 都不处理这个事件,这个事件最后会返回到 activity 的 onTouchEvent 进行处理。

事件分发具体细节较为复杂,但基本流程就是上面的伪代码。事件分发机制是处理滑动冲突的根本,知道了原理,遇到问题再多看看源码就行了。