定义:当用户触摸屏幕时,将产生触摸行为(Touch事件)
四种:
1 MotionEvent.ACTION_DOWN
2 MotionEvent.ACTION_ACTION_MOVE
3 MotionEvent.ACTION_ACTION_UP
4 MotionEvent.ACTION_CANCEL
主要有三个方法
接下来我们从源码的角度来看看Activity中事件是怎样分发的
打开Activity的源码,找到 dispatchTouchEvent(MotionEvent ev)
这个方法,Activity的事件分发就是从这里开始的
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我们可以看到,首先会判断 这个事件 是否的 DOWN
事件,如果是,就执行onUserInteraction()
这个方法
定位到onUserInteraction() 这个方法中来
public void onUserInteraction() {
}
我们发现这个方法中没有任何的实现,是一个空的方法,该方法主要的作用是实现屏保功能
,并且当此 Activity 在栈顶的时候,触屏点击 Home、Back、Recent 键等都会触发这个方法
,我们可以在自己的Activity类中重写
这个方法,实现自己想要的功能
我们再来看看下面这段代码
if (getWindow().superDispatchTouchEvent(ev))
{
return true;
}
getWindow().superDispatchTouchEvent(ev)
这个getWindow()
拿到的是什么了? 我们定位到getWindow()
方法里面来看看
public Window getWindow()
{
return mWindow;
}
可以看到 调用这个方法返回的就是一个 Window
对象,那么这个window
类里面又是什么了?进入window
类的源码看看
/**
* 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.
*
* The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
public static final int FEATURE_OPTIONS_PANEL = 0;
/** Flag for the "no title" feature, turning off the title at the top
* of the screen. */
public static final int FEATURE_NO_TITLE = 1;
/**
* Flag for the progress indicator feature.
*
* @deprecated No longer supported starting in API 21.
*/
翻译上面类的注释
/ **
*用于顶层窗口外观和行为策略的抽象基类。 一个
*此类的实例应用作添加到
*窗口管理器。 它提供标准的UI策略,例如背景,标题
*区域,默认密钥处理等
*
* 此抽象类的唯一现有实现是
* android.view.PhoneWindow,您需要在需要
*窗口。
* /
我们可以知道,这是一个抽象类,用于我们视图窗口的管理,并且它只有唯一一个子类 PhoneWindow
所以
getWindow().superDispatchTouchEvent(ev)
这行代码实际上是调用了 PhoneWindow 这个子类里面 实现的 superDispatchTouchEvent(ev)
这个方法
我们定位到 PhoneWindow
里面 superDispatchTouchEvent(ev)
这个方法里面来
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
可以看到 这个方法执行的是一个 mDecor.superDispatchTouchEvent(event)
这个mDecor
是什么了?
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
可以看到 它是 DecorView
的一个对象 ,从上面的注释翻译就是说 DecorView
是我们界面的顶层视图
我们再来到 DecorView 源码看看
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
private static final String TAG = "DecorView";
private static final boolean DEBUG_MEASURE = false;`
而FrameLayout 又是继承于 ViewGroup FrameLayout extends ViewGroup
public class FrameLayout extends ViewGroup
mDecor
这个对象我们弄明白了,它就是DecorView
的一个对象而已,所以mDecor.superDispatchTouchEvent(event)
执行的就是 DecorView里面的 superDispatchTouchEvent(event)
的这个方法
我们定位到DecorView这个方法里面来
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
可以看到 这个方法里面再做什么了? super.dispatchTouchEvent(event)
在执行这一行代码,super
这个关键字
这明显是在调用父类的方法 那么根据我们上面的分析得知,DecorView
的父类 是 FrameLayout
,
我们打开 FrameLayout源码,并没有发现 dispatchTouchEvent
这个方法,但是我们知道 FrameLayout
它的父类是ViewGroup
,根据继承的特性,这个方法必然在 ViewGroup
里面
所以 super.dispatchTouchEvent(event)
这个方法调用的是 ViewGroup
里面的 dispatchTouchEvent(event)
打开ViewGroup 源码。可以看到这个方法了
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
Activity--------》PhoneWindow--------》DecorView--------ViewGroup
也就是说,当我们用手指触摸手机屏幕,这当中的一系列事件传递到了ViewGroup中,在ViewGroup里进行事件的分发工作
我们知道的事件传递到了ViewGroup
中的dispatchTouchEvent(MotionEvent ev)
这个方法里面,我们接下来分析一下这个方法里面做了些什么事情?
// 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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
首先判断是否是 MotionEvent.ACTION_DOWN)
事件,如果是
cancelAndClearTouchTargets(ev);//取消并清除所有触摸目标。
resetTouchState() //重置所有触摸状态,为新的周期做准备。
接下来我们分析这段代码
// Check for interception.
final boolean intercepted;//定义一个bolean类型的变量,用于标记事件是否拦截
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//进行为运算
if (!disallowIntercept) {//如果为运算出来的结果不为true的画
intercepted = onInterceptTouchEvent(ev);//进行是否拦截的判定计算
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;//当上面条件不满足时,赋值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事件,就继续拦截
intercepted = true;
}
final boolean intercepted;
//定义一个boolean
类型的变量,用于标记事件是否拦截
当满足了 actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)这两个条件后
才进行 intercepted 的赋值操作
这里还需要一个值得注意的地方, 当事件为DOWN
时 执行了这两个方法 cancelAndClearTouchTargets(ev); resetTouchState();
,把很多状态重置后
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//进行为运算
这段代码运行的结果永远为false ,所以 intercepted = onInterceptTouchEvent(ev);
一定会执行,下面我们注意这个方法requestDisallowInterceptTouchEvent(boolean disallowIntercept)
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
这个方法一般由子view来调用,用来决定viewgroup
是否拦截除DOWN事件以外的其它事件,假设我们把这里的onInterceptTouchEvent(ev) 重写并始终返回为true,那么子view将会永远得不到事件的分发,所有的事件都会被ViewGroup拦截。
平时开发过程中可能会有这种需求:在触控的时候,按下那下(ACTION_DOWN
)需要子View
进行事件反馈,但在滑动(ACTION_MOVE)或者抬起(ACTION_UP
)的时候需要父布局进行拦截操作,这个时候就要用到我们的requestDisallInterceptRouchEvent(boolean disallowIntercept)
方法了,该方法可以在子类
通过调用getParent().requestDisallInterceptRouchEvent(true)
来让父布局禁用拦截事件功能,这样子view只会相应DOWN事件,不会得到后续事件,因为父布局已拦截了
下面我们继续分析
intercepted = onInterceptTouchEvent(ev);
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
中的onInterceptTouchEvent(MotionEvent ev)
这个方法,默认是返回false的,也就是说ViewGroup默认不会拦截子view的事件,当为false时,所有的事件都会传递给子view
如果不拦截 intercepted
就为false
,就会执行到下面代码,源码没有全部贴出,
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//=====================================================
//如果newTouchTarget 等于空并且 子view 的数量不为0
//=====================================================
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//=====================================================
//for循环倒序遍历,得到子view
//=====================================================
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//这个子view不可见或者在执行动画 又或者 触摸的范围不在这个子view的范围之内,就继续遍历
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//如果有满足上面条件的子view,就将子view(child)当做参数传递进去,执行
//dispatchTransformedTouchEvent()这个方法
//如果这个方法返回true,表示子view消耗了此事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
//判断这个子view是可见的并且没有执行动画
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
newTouchTarget = addTouchTarget(child, idBitsToAssign);
查看这个方法
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
表示让mFirstTouchTarget 指向消耗事件的子view,如果所有的子view都不消费事件,mFirstTouchTarget 就为null
我们来到
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)
这个方法中
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
handled = super.dispatchTouchEvent(event);
} else {
//如果子view child 不为空 把这个事件传递给子view
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//这样就完成了事件从ViewGroup到子view的传递过程
handled = child.dispatchTouchEvent(event);
//当事件处理完毕,就会返回一个 handled 表示子view是否消费的此次事件
//当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
//也就是调用 View的 dispatchTouchEvent(event)
handled = super.dispatchTouchEvent(event);
if (child == null) {
//当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
当child为空时 如果此时容器为viewGroup类型,当viewGroup类型的容器中重写了onTouchEvent()
方法,那么 super.dispatchTouchEvent(event); 实际上是调用了viewGroup类型的容器中 重写的onTouchEvent()
View中有 onTouchEvent()这个方法,viewGroup继承于View,但是没有重新onTouchEvent()这个方法,如果此时
我们自定义控件继承了viewGroup,并且重写了onTouchEvent()根据继承的关系执行 super.dispatchTouchEvent(event)
这个调用又会调用View当中的onTouchEvent()方法,而此时我们自定义的控件从写了这个方法,那么最终会回调我们自定义控件中的onTouchEvent()方法
handled = super.dispatchTouchEvent(event);
} else {
//如果子view child 不为空 把这个事件传递给子view
回调子view的onTouchEvent() 如果子view重写了onTouchEvent()
handled = child.dispatchTouchEvent(event);
}
// Dispatch to touch targets. 没有触摸目标,因此请将其视为普通视图。也就是没有子view消费事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
当ViewGroup进行事件分发工作时,如果onInterceptTouchEvent()
方法返回true,拦截了此次事件,那么此次事件将不再传递给子view了,从而调用ViewGroup自身的onTouchEvent()
方法
如果不拦截,就将此次事件继续传递给子view的dispatchTouchEvent()方法
,子view的dispatchTouchEvent()
方法里又继续分发给自己,也就是分发给自己的onTouchEvent()方
法,
注意 :dispatchTouchEvent()这个方法都只做事件分发工作,真正的事件处理工作都是由onTouchEvent()方法来完成的