一、带着问题出发
我们手触摸点击屏幕的时候,触摸、点击事件是如何分发的呢?
布局中的控件是如何获取到按键事件的呢?
布局中有多个控件,如何只让指定的控件接收到相关的事件呢?
二、说在前面
下面来大体说下事件分发涉及到的几个类和相关方法:
Android的事件分发顺序是:Activity ----> ViewGroup ----> View
涉及到的几个重要方法:
dispatchTouchEvent();
onInterceptTouchEvent();
onTouchEvent();
dispatchTouchEvent负责事件的分发,onInterceptTouchEvent()是ViewGroup中判断是否进行事件拦截的方法,onTouchEvent() 是dispatchTouchEvent() 中执行的方法。
下面就分别来看下事件在Activity、ViewGroup、View 的分发机制。
三、Activity 的事件分发机制
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// >>> 分析1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow.java
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
}
public class FrameLayout extends ViewGroup {
...
}
分析1:getWindow()是获取当前的window,调用到的是PhoneWindow,PhoneWindow 的superDispatchKeyEvent()中调用了DecorView 的superDispatchKeyEvent();而DecorView中调用的是super.dispatchTouchEvent(ev);
DecorView继承的是FrameLayout,FrameLayout又是继承的ViewGroup,所以最终会调用到ViewGroup的dispatchTouchEvent(ev);
四、ViewGroup的事件分发机制
ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
//>>> 分析2
intercepted = onInterceptTouchEvent(ev);
...
if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
...
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
}
分析2:这里会有intercept,是否进行事件拦截的判断,该值是根据onInterceptTouchEvent()的返回值来判断,默认是返回的fale。
如果需要对事件进行拦截,可以重写ViewGroup的onInterceptTouchEvent()方法,直接return true,onInterceptTouchEvent()方法只有ViewGroup有,Activity和View都没有。
intercept 值为true,则进行事件拦截,执行的是super.dispatchTouchEvent(event),也就是View的dispatchTouchEvent(ev)方法,这个下面会分析.
intercept 值为false,则会遍历ViewGroup的子view,如果是处于子View的触摸区域,则会调用子view的dispatchTouchEvent();
五、View的事件分发机制
View.java
public boolean dispatchTouchEvent(MotionEvent event) {
// >>>分析3
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// >>>分析4
if (!result && onTouchEvent(event)) {
result = true;
}
}
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
public boolean onTouchEvent(MotionEvent event) {
...
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
......
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
分析3:如果view设置了OnTouchListener监听,这会会执行OnTouchListener的监听,如果onTouchLister return true,则表示事件被消费掉了,会直接return true,事件分发结束;可以重写setOnTouchListener()方法.
分析4:OnTouchListener中返回false,则会往下执行onTouchEvent(ev)方法,onTouchEvent(ev)中会执行performClick()方法,这时如果view设置了onClick监听,会执行onClick方法。
这里也可以看到,onTouchListener的执行会先于onClickListener.
六、最后总结
通过上面简单的代码跟读,我们知道点击Activity时,点击事件最先传递到Activity.java dispatchTouchEvent()中进行分发,
dispatchTouchEvent()中会调用到ViewGroup 中的dispatchTouchEvent()方法,
ViewGroup 中有个重要的onInterceptTouchEvent()方法来判断是否进行事件拦截,该方法默认return false,如果我们希望进行事件拦截,
可以重写该方法,return true,则事件会被消费掉,
不会再分发给子view;onInterceptTouchEvent() return false的时候,
会继续往下走,往下会遍历子view,如果当前点击的区域是相应子view的区域,
则会调用到子view的dispatchTouchEvent()方法,view 的dispatchTouchEvent()中会
先优先判断是否设置了onTouchListener监听,onTouchListener return false,后面才会执行view 的onClick监听。
=======================================================================
*本人从事Android Camera相关开发已有5年,
*目前在深圳上班,
*小伙伴记得点我头像关注,也可以关注我的微信公众号【小驰笔记】,希望和更多的小伙伴一起交流 ~
---- 2020.01.06 深圳