Android 事件分发机制分析及源码详解
Posted xyTianZhao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 事件分发机制分析及源码详解相关的知识,希望对你有一定的参考价值。
android 事件分发机制分析及源码详解
文章目录
一般在实际开发中,我们很少主动去处理相关滑动处理,所以就很少关注事件分发相关机制。因为系统已经帮我们处理好了,如:ScrollView、ViewPage、ListView 等。
这里我们就以事件分发为入口,来分析一下事件分发机制与滑动冲突的解决方法。
学习 Android 开发过程中,或多或少都有听过事件分发机制,就算没听过,如果有面试经历,也大有几率被问到过吧。毕竟在现在,这个分发机制以及算是烂大街的题目了。
事件的定义
当用户触摸屏幕时,产生的一系列行为( Touch 事件)。
系统源码中通过 MotionEvent
这个类来描述这一系列相关行为。这个类中也定义了许多与事件相关的常量与变量,如事件类型、事件的相关属性等。这里就只贴出主要的四个相关类型
public final class MotionEvent extends InputEvent implements Parcelable
//按下事件:手指刚触碰屏幕
public static final int ACTION_DOWN = 0;
//松开事件:手指从屏幕松开
public static final int ACTION_UP = 1;
//移动事件:手指在屏幕上滑动
public static final int ACTION_MOVE = 2;
//取消事件:非人为因素取消
public static final int ACTION_CANCEL = 3;
事件分发序列模型
分发序列
正常情况下,一次手指触摸屏幕的行为会发出一系列点击事件,可以分为如下两种情况:
- 点击屏幕后立即松开:DOWN->UP
- 点击屏幕滑动后再松开:DOWN->MOVE···->MOVE->UP
分发模型
我们在从宏观角度了解一下事件的分发总流程,总体来说就是:U型模型
事件先从 Activity
出发,然后传递给 DecorView(ViewGroup)
,经过 ViewGroup
然后在传递给相应的子 View 进行处理。如果不处理,则按照原路径逐级向上返回,直至传递到 Activity
结束。
如下流程图表述应该比较容易理解。
事件分发对象及相关方法
通过上面的分发序列和分发模型,可以看出,一个事件的分发主要涉及三个对象:Activity、ViewGroup和View。
- Activity:控制生命周期和处理事件
- ViewGroup:一组 View 的集合(可以包含多个子 View)
- View:所有 UI 组件的基类
而这三个分发对象都有相应的事件处理方法
- dispatchTouchEvent(MotionEvent event)
用来进行事件分发 - onInterceptTouchEvent(MotionEvent ev)
判断是否拦截事件(值存在于 ViewGroup 中) - onTouchEvent(MotionEvent event)
处理相关事件
源码分析
到这里我们对整个大致流程已经有了相关的印象了,接下来我们在从源码的角度来看看,事件是怎么传递的。代码省略了不重要的部分,并且加上了相关注释,就不过多进行解释了。
事件从 Activity
的 dispatchTouchEvent
方法开始向下分发进行处理。
public class Activity
public boolean dispatchTouchEvent(MotionEvent ev)
...
//调用 window 实现类 PhoneWindow 的 superDispatchTouchEvent 方法
if (getWindow().superDispatchTouchEvent(ev))
return true;
//如果上面不进行处理,则调用 Activity 的 onTouchEvent 进行处理
return onTouchEvent(ev);
public boolean onTouchEvent(MotionEvent event)
//如果点击区域外,则销毁当前界面,否则不进行任何处理
if (mWindow.shouldCloseOnTouch(this, event))
finish();
return true;
return false;
接下来在看看 PhoneWindow
怎么处理
public class PhoneWindow extends Window implements MenuBuilder.Callback
// This is the top-level view of the window, containing the window decor.
//窗口的顶层视图,相当于上面模型中的 跟ViewGroup
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event)
return mDecor.superDispatchTouchEvent(event);
public class DecorView extends FrameLayout
public boolean superDispatchTouchEvent(MotionEvent event)
//最终会调用 ViewGroup 的 dispatchTouchEvent 方法
return super.dispatchTouchEvent(event);
先看看 ViewGroup
中的拦截逻辑
public abstract class ViewGroup extends View
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
...
boolean handled = false;
...
//进行安全校验和过滤,一般都为 true
if (onFilterTouchEventForSecurity(ev))
...
// 检测是否需要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept)
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
else
intercepted = false;
else
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
...
if (!canceled && !intercepted)
...
if (newTouchTarget == null && childrenCount != 0)
for (int i = childrenCount - 1; i >= 0; i--) //倒叙遍历 子View
...
//将事件分发给 子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
...
//返回 true 表示该 view 处理事件,然后将该 view 赋值给 目标view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
...
...
...
...
return handled;
//进行一些安全性校验和过滤
public boolean onFilterTouchEventForSecurity(MotionEvent event)
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
// Window is obscured, drop this touch.
return false;
return true;
//是否拦截事件,在 ViewGroup 中一般返回的 false
public boolean onInterceptTouchEvent(MotionEvent ev)
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY()))
return true;
return false;
在看看 ViewGroup
的分发逻辑
public abstract class ViewGroup extends View
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)
final boolean handled;
...
if (child == null)
//调用 View 的 dispatchTouchEvent 方法
handled = super.dispatchTouchEvent(event);
else
//调用 子View 的 dispatchTouchEvent 方法
handled = child.dispatchTouchEvent(event);
event.setAction(oldAction);
...
return handled
上面事件已经从 Activity
分发到了 ViewGroup
,再从 ViewGroup
的 dispatchTransformedTouchEvent
方法调用了子 View
的 dispatchTouchEvent
方法,接下来在看看 View 中对于事件是怎么处理的。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource
public boolean dispatchTouchEvent(MotionEvent event)
...
boolean result = false;
//当前View是否可见(未被其他窗口遮盖住,且未隐藏)
if (onFilterTouchEventForSecurity(event))
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event))
//如果设置了 OnTouchListener
//则先响应 OnTouchListener.onTouch 方法
result = true;
// 当 onTouch 返回 false,在执行 onTouchEvent 处理相关事件,如果处理,则返回 true
if (!result && onTouchEvent(event))
result = true;
...
...
return result;
public boolean onTouchEvent(MotionEvent event)
...
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
...
//如果 View 可点击 或者 可长按,则最终一定 return true
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP)
switch (action)
//抬起,判断是否处理点击事件
case MotionEvent.ACTION_UP:
break;
//按下,处理长按事件
case MotionEvent.ACTION_DOWN:
break;
//移动,检测触摸是否划出了控件,移除响应事件
case MotionEvent.ACTION_MOVE:
break;
return true;
...
return false;
事件分发总结
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外
- ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false, View没有onlnterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
- View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的clickable和longClickable同时为false), View的longClickable默认都为false, clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false.
- View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true
- onClick会响应的前提是当前View是可点击的,并且收到了ACTION-DOWN和ACTION-_UP的事件,并且受长按事件影响,当长按事
件返回true时, onClick不会响应。 - onLongClick在ACTION-DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener
- 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束正常情况下,一个事件序列只能被一个view拦截并且消耗。
- 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onlnterceptTouchEvent不会再调用。
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用)。
以上是关于Android 事件分发机制分析及源码详解的主要内容,如果未能解决你的问题,请参考以下文章
从源码角度分析Android 事件分发机制以及常见滑动冲突解决方案
从源码角度分析Android 事件分发机制以及常见滑动冲突解决方案
Android6.0 ViewGroup/View 事件分发机制详解
Android查缺补漏(View篇)--事件分发机制源码分析
朝花夕拾Android自定义View篇之Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象