事件的定义
定义:当用户触摸屏幕时,将产生的触摸行为(Touch事件)
事件类型
- MotionEvent.ACTION_DOWN 手指刚接触屏幕
- MotionEvent.ACTION_UP 手指从屏幕移开
- MotionEvent.ACTION_MOVE 手指在屏幕上滑动
- MotionEvent.ACTION_CANCEL 非人为因素取消
事件序列
正常情况下,手指触摸屏幕的行为会发出一些列事件:
- 点击屏幕后立即松开,事件序列为DOWN—>UP
- 点击屏幕滑动一会儿松开,事件序列为DOWN—>MOVE…MOVE…MOVE—>UP
- 还有一种非人为因素的取消,CANCEL
事件分发对象
- Activity:控制生命周期&处理事件
- ViewGroup:一组View的集合(含多个子View)
- View:所有UI组件的基类
事件分发的主要方法
- dispatchTouchEvent(MotionEvent ev) -> 用来进行事件分发
- onInterceptTouchEvent(MotionEvent ev) -> 判断是否拦截事件(只存在于ViewGroup中)
- onTouchEvent(MotionEvent ev) -> 处理拦截事件
事件分发
Activity的事件分发
当事件发生时,首先会调用Activity的dispatchMotionEvent()方法,跟踪源码发现最终调用了ViewGroup的dispatchMotionEvent()方法,进入源码查查:
//Activity中的方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //空方法供子类实现
}
if (getWindow().superDispatchTouchEvent(ev)) {
//调用window的superDispatchTouchEvent(),由于在Window中是一个抽象方法,而PhoneWindow是其唯一实现类,又调用到PhoneWindow的方法
return true;
}
return onTouchEvent(ev);
}
//Window中
//这是一个抽象方法,需要寻找其唯一实现类PhoneWindow的方法
public abstract boolean superDispatchTouchEvent(MotionEvent event);
//PhoneWindow中
public boolean superDispatchTouchEvent(MotionEvent event) {
//返回了DecorView的superDispatchTouchEvent(),而DecorView其实就是一个Framelayout,最终就调用到了ViewGroup中的superDispatchTouchEvent()
return mDecor.superDispatchTouchEvent(event);
}
//ViewGroup中
public boolean dispatchTouchEvent(MotionEvent ev) {
//省略大量代码
如果handled = true,在Activity中就完成了此次的事件分发,如果handled = false将会调用onTouchEvent()方法
return handled;
}
//在Activity中
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
除了这里返回true时结束Activity,
finish();
return true;
}
//一律不处理,本次事件分发结束
return false;
}
//此方法主要在判断是否是点击了Activity外部且是ACTION_DOWN事件,其实典型的案例就是dialog型的activity
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
ViewGroup的事件分发
由于我们的一个完整的分发事件开始于dispatchTouchEvent()方法,并且开始的事件一定是ACTION_DOWN,所以我们来看源码
//ViewGroup中
public boolean dispatchTouchEvent(MotionEvent ev) {
//省略的代码
//(1)、Handle an initial down.所以在这里需要初始化一些东西
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//这是源码注释,取消和清除所有的触摸状态 Cancels and clears all touch targets.
cancelAndClearTouchTargets(ev);
//源码注释,重置所有触摸状态,为新的循环做准备。Resets all touch state in preparation for a new cycle.
resetTouchState();
//此刻初始化的操作完成
}
//(2)、Check for interception. 检查是否拦截
final boolean intercepted;//标记是否拦截的状态
//先判断是否是ACTION_DOWN事件,或者是其他子控件处理了(mFirstTouchTarget != null表示有其他子控件处理)
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//这里判断是否拦截的操作FLAG_DISALLOW_INTERCEPT那么这个在哪里设置呢,转到下一个片段
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//需要拦截就调用此方法,这里默认都是返回false,继续向下传递处理,如果为true只能是子View继承了去修改控制,那么直接让当前的ViewGroup的onTouchEvent处理不会向下传递了
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.
//不是ACTION_DOWN事件也没有子控件处理那么久拦截传递到activity去处理
intercepted = true;
}
/**
* mGroupFlags的FLAG_DISALLOW_INTERCEPT的设置位置,在ViewGroup里有这样一个方法,通常在子控件中实现调用,所有我们在处理事件分发是需要用到此方法
**/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//省略源码
}
}
ViewGroup的onInterceptTouchEvent()分析:
public boolean onInterceptTouchEvent(MotionEvent ev) {
//继续向下传播,为true的时候就直接发送给当前viewGroup的onTouchEvent()方法处理了且不继续向下传播
return false;
}
- 可以通过实现这个方法去拦截所有的touch事件;这个可以让viewgroup监视分发给它的子view的事件,可以在任何时候取得当前手势事件的控制权;
- down事件可能会被这个viewGroup的一个子孩子处理掉,或者给自己的onTouchEvent()事件处理了。这意味着你需要实现onTouchEvent()方法然后返回true,然后你就会继续接收到剩下的手势事件(而不是继续向上找一个父View去处理这个事件)。同时,onTouchEvent()方法返回true的时候,viewGroup的onInterceptTouchEvent()方法就不会再接收到陆续的方法事件,所有的touch事件会正常的直接给onTouchEvent()方法。
- 如果这个方法返回false,接下来的后续事件都会先传递到这里,然后再传递给处理这个事件的目标view的onTouchEvent。
- 如果你这里返回true,你不会再接收到后续的事件,目标view会接受到相同的事件产生的ACTION_CANCEL;其他的事件也会直接传递到你的onTouchEvent,而再也不会在onInterceptTouchEvent()方法里捕捉到。
- 如果返回true就会从他的子孩子抢占过来这个事件且将这个事件分发给当前ViewGroup的onTouchEvent()事件处理。当前的目标View会接受到ACTION_CANCEL事件,且不会再接受到后续的事件了。
继续ViewGroup的dispatchTouchEvent()分析
public boolean dispatchTouchEvent(MotionEvent ev) {
//没有被取消也没有被拦截
if (!canceled && !intercepted) {
//这里主要做了一件事情,有哪些子控件能够去接受此次事件的分发
for (int i = childrenCount - 1; i >= 0; i--) {
//for遍历所有的子控件
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
//判断是否在我点击的范围内是能够接收此次事件的view,如果不是则继续遍历
continue;
}
//继续执行到这里
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {}
}
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//这里都是做一些判断,最终都调用到了View的dispatchTouchEvent()方法
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent); // 调父类的dispatchTouchEvent()
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);// 调子类的dispatchTouchEvent()
}
}
View的事件分发
View中同样也是从dispatchTouchEvent()开始的事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll(); //先判断是否是点击事件,然后停止其他的一些滚动事件
}
//这里选择调用那种onTouch()
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//这个监听不为空那么执行此方法
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果上面没有执行这里就判断进入并执行onTouchEvent()
if (!result && onTouchEvent(event)) {
result = true;
}
//从上看出增加了监听的事件优先级更高一些
}
}
View的onTouchEvent()执行流程分析
public boolean onTouchEvent(MotionEvent event) {
//先有一段代码判断是否可点击状态
//如果设置代理事件,那么直接交给代理去处理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果View可点击,可长按最后一定返回true
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//接下来就会进入switch
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下,主要去处理长按事件
break;
case MotionEvent.ACTION_MOVE:
//移动,判断触摸是否划出了控件,移除响应事件
break;
case MotionEvent.ACTION_UP:
//抬起,判断是否处理点击事件
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
return false;
}
事件分发模型流程图
事件分发结论
- 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列
事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束; - 正常情况下,一个事件序列只能被一个View拦截并且消耗;
- 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不会再调用;
- 某个View-旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事 件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_ DOWN除外;
- 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状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
- onClick会响应的前提是当前View是可点击的,并且收到了ACTION_ DOWN和ACTION_ _UP的事件,并且受长按事件影响,当长按事件返回true时,onClick不 会响应;
- onLongClick在ACTION_ DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener.