概述
最近在复习androidUI的相关知识点,在网上看了一些关于事件分发的文章与视频,因此在此记录一下自己的理解过程。
首先思考2个问题:
- 什么是事件?
- 什么是事件分发机制?
用户与手机的交互会产生一系列的事件 ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL。每一个事件都会封装成一个ACTIONEVENT.
事件分发主要解决由谁来处理事件的问题,当你点击Actiivty上的某一个控件时实际点击了不止一个控件,按照下图的显示。

当用户点击了view1时,实际上用户也点击到了ViewGroupA、RootView、DecorView,那事件到底交由哪个控件去处理?另外事件是由一系列的事件序列组成的,从DOWN–>MOVE–>UP,无法割裂开来,要么被忽略,要么都被某一个view所消费。
事件分发流程
如果在屏幕点击了一个控件View,最终这个控件View消费了这个事件的话,那大概的分发流程应该是Activity–>ViewGroup–>View。
针对分发流程我们需要关注下面几个方法:
dispatchTouchEvent() //分发触摸事件 (dispath 就是分发的意思)
onInterceptTouchEvent() // 拦截触摸事件 (Intercept 拦截的意思)
onTouchEvent() // 执行触摸事件 (on 可以理解为执行)
无论是Activity,View,ViewGroup都会调用dispatchTouchEvent 与 onTouchEvent。而onInterceptTouchEvent比较特殊,只有在ViewGroup中有。
先看前面一段事件是如何从Activity传递到ViewGroup的。

当我们查看Activity的dispatchTouchEvent()方法,向上追溯可以看到如下的调用路径:
Activity–>PhoneWindow–>DecorView–>ViewGroup
Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
ViewGroup的dispatchTouchEvent()比较长,但总体执行逻辑可以按照下面伪代码表示
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){
//拦截 将事件交给onTouchEvent()处理
consume = onTouchEvent(ev);
}else{
//不拦截 将事件分发给子view
consuem = child.dispatchTouchEvent(ev);
}
return consume;
}
这里分发的流程就从ViewGrop分发到了View的DispatchTouchEvent.
看下View的DispatchTouchEvent 方法:
public boolean dispatchTouchEvent(MotionEvent event) {
//一系列操作..
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
//又是一系列操作
return result;
}
上述代码只需要关注2个方法 一个onTouch方法,一个onTouchEvent()方法。了解onTouch方法是在onTouchEvent()之前执行,假如设置了View的mOnTouchListener,那onTouch()方法就会执行。
下面看下View的onTouchEvent()方法,看下它是如何来执行触摸事件的。
public boolean onTouchEvent(MotionEvent event) {
//一系列操作1
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//一系列操作2
if (clickable) {
switch (action) {
case MotionEvent.ACTION_UP:
//一系列操作3
break;
case MotionEvent.ACTION_DOWN:
//一系列操作4
break;
case MotionEvent.ACTION_CANCEL:
//一系列操作5
break;
case MotionEvent.ACTION_MOVE:
//一系列操作6
break;
}
return true;
}
return false;
}
事件最终分发到View(View消费掉事件)
在onTouchEvent方法中,假如view是可以点击或者长按,无论是DOWN,UP ,MOVE,还是CANCEL 方法都会返回true,那就表示这个事件在view中被消费掉。那view的dispatchTouchEvent() 也会返回ture,继续往下追溯,ViewGroup的DispatchTouchEvent()返回true,Activity的dispatchTouchEvent() 返回true。
事件最终分发到View(View没有消费掉事件)
假如View的没有消费掉事件(返回false),往前回溯,那View的dispatchTouchEvent() 也会返回false,ViewGroup的dispatchTouchEvent()返回false,事件最终会通过Activity的dispatchTouchEvent()方法交由onTouchEvent()方法来处理。
事件被VIewGroup拦截(ViewGroup消费掉事件):
当然上述的图默认表示的是ViewGroup的onInterceptTouchEvent()返回false的逻辑。假如ViewGroup的onInterceptTouchEvent()返回true。那代码很明显就要走ViewGroup的onTouchEvent()方法,此时如果子类重写了onTouchEvent()方法并消费了事件,方法调用关系如下图:
小结一下
假如ViewGroup拦截了View的事件,那么最终事件就被分发到ViewGroup的onTouchEvent() 方法中。
如果没有拦截,事件就被分发到了View的onTouchEvent()方法中。
此时的View默认是可以点击的(比如Button),那子View就默认消费了事件。如果子View不能点击(比如TextView),那子View就不会消费事件。
还剩下一个问题,之前说View要么不处理事件,要么就是整个事件序列一起处理,这部分是如何实现的?
这部分源码只看了个大概(没看太懂…)。
大致的流程:
在ViewGroup的dispatchTouchEvent() 方法中。如果接收到的事件是ACTION_DOWN时,会将处理DOWN事件的子View 保存到mFirstTouchTarget变量中。后面的MOVE,UP事件来的时候,通过调用dispatchTransformedTouchEvent(child)方法来执行child的dispatchTouchEvent()方法。
Click与LongClick事件
Click与LongClick事件是如何进行判断的呢?
Click事件对应较短时间内(<0.5s)的一次DOWN和UP事件。
LongClick事件对应较长事件(>0.5s)的一次DOWN事件和UP事件。
而DOWN跟UP事件都是发生在View的onTouchEvent()方法中。因此可以初步判断Click事件与LongClick事件都可以在View的onTouchEvent()事件中找到调用链。
public boolean onTouchEvent(MotionEvent event) {
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable ) {
switch (action) {
case MotionEvent.ACTION_UP:
//一系列操作
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
//一系列操作
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//一系列操作
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
//一系列操作
break;
case MotionEvent.ACTION_DOWN:
//一系列操作
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
//一系列操作
break;
case MotionEvent.ACTION_CANCEL:
//一系列操作
break;
case MotionEvent.ACTION_MOVE:
//一系列操作
break;
}
return true;
}
return false;
}
通过上面的代码可以看到在UP事件中有一个mPerformClick对象;在DOWN事件中有个checkForLongClick()方法。通过名字我们似乎可以看出UP事件处理的Click相关的回调。而DOWN事件处理的LongClick相关的回调。
继续往下跟代码发现确实如此。在View的performClickInternal()方法
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
performClick() 方法
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
我们看到了li.mOnClickListener.onClick(this);
同样在也是在View的checkForLongClick()方法:
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
CheckForLongPress() 对象中
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
private boolean mOriginalPressedState;
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
//...
}
performLongClick()方法。继续往下多跟几个方法
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
//...
}
最终可以看到li.mOnLongClickListener.onLongClick() 这一行代码。
基本可以验证我们上面的猜想。
在View的onTouchEvent() 方法的UP事件中调用了Click事件,在DOWN事件中调用了LongClick事件。
结论
- 一个时间序列从手指接触屏幕到手指离开屏幕,在这个过程产生的一系列事件,从DOWM事件开始,一系列的MOVE 事件,以UP事件结束
- 正常情况下,一个事件序列由一个view处理
- 某个ViewGroup一旦决定拦截,那么所有的事件序列都会由它的onTouchEvent()处理,并且它的onInterceptTouchEvent()不会再调用
- 某个View一旦开始处理事件,假如它没有消耗DOWN事件,那么同一事件序列的其它事件都不会再交给它处理。会交由它的父元素处理(父元素的onTouchEvent()被调用)
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View
- ViewGroup默认不拦截事件,即onInterceptTouchEvent默认返回false.View没有onInterceptTouchEvent方法,一旦有事件被拦截,那么它的onTouchEvent方法会被调用
- View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable与longClickable同时为false)。View的longClickable默认都是false,clickable要分情况,比如button的clickable默认是true,Textview的clickable默认是false
- View的enable属性不影响onTouchEvent()的默认返回值。哪怕一个View是disable状态,onTouchEvent()的返回值只跟clickable 与 longClickable 相关
- onClick的响应的前提是view是可点击的,并且收到了DOWN与UP事件,并且受长按事件的影响,当长按事件返回true,onClick不会响应
- onLongClick再DOWN事件中判断,要想执行长按事件该view必须是longClickable的并且设置了onLongClickListener.
引用:
https://blog.youkuaiyun.com/weixin_41101173/article/details/79685305?utm_source=blogxgwz1
https://www.gcssloop.com/customview/dispatch-touchevent-theory
https://zhuanlan.zhihu.com/p/27608989
https://blog.youkuaiyun.com/guolin_blog/article/details/9097463
https://blog.youkuaiyun.com/guolin_blog/article/details/9153747
900

被折叠的 条评论
为什么被折叠?



