触摸事件
- 从Activity的dispatchTouchEvent()进行分析。
触摸事件概念
- 触摸事件是由一个触摸按下事件、N个触摸滑动事件和一个触摸抬起事件组成的,通常的一个触摸事件中只能存在一个触摸按下和一个触摸抬起事件,但是触摸滑动事件可以有零个或者多个。
好了,知道这个概念以后,下面我们就具体看一下Activity中的dispatchTouchEvent的实现逻辑。
首先会判断是否是按下事件,是,则执行onUserInteraction()方法,而onUserInteraction()方法有执行什么逻辑呢?
public void onUserInteraction() {
}
可以看见它只是Activity中一个空方法,通过方法介绍:
Called whenever a key, touch, or trackball event is dispatched to the activity. Implement this method if you wish to know that the user has interacted with the device in some way while your activity is running. This callback and {@link #onUserLeaveHint} are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.
理解上就是用户在触屏点击,按home,back,menu键都会触发此方法。
回到Activity的disaptchTouchEvent()方法中,调用了getWindow().superDispatchTouchEvent(ev)方法,而getWindown()返回的mWindown实例,其实就是phoneWidown对象。这里superDispatchTouchEvent()返回true,则直接返回true,不会再执行onTouchEvent()方法,反之,继续执行onTouchEvent().我们继续看下getWindow().superDispatchTouchEvent(ev)方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
可以看到继续调用了mDecor.superDispatchTouchEvent方法,而mDecor是我们Activity显示ViewTree的根View,并且mdecor是FrameLayout的一个子类,DecorView是phoneWindow中一个final内部类,是window界面的最顶层View.
View的DispatchTouEvent()
- View 的dispatchTouchEvent()源码如下:
有图可知onTouch()是优于onTouchEvent()执行的,但是Ontouch()执行的前提是控件enable属性是为true并且mOnTouchlistener不为空,ontouch()才会执行;而且可知ontouch()放回true,ontouchEvent()将不会再执行
* View的onTouchEvent()方法:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
//如果View是disable并且View是可以点击或长按的直接返回true,表示该View一直消费Touch事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果View是clickable或者longClickable的onTouchEvent就返回true, 否则返回false
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
//当手指在View上面滑动超过View的边界,
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
有onTouchEvent()源码分析我们可以知道:
- 如果一个View是disable的并且该View是可以点击或者长按的,ontouChEvent直接返回true,表示该View一直消费touch事件。
- 如果一个View是enable的,并且是clickable的或longclickable,onTouchEvent()会执行下面的逻辑并返回true;
- 综合1、2可知一个View是clickable或者longclickable的,会一直消费touch事件的
- 而普通的View(既不是clickable也不是longclickable)不会消费touch事件(只会执行action_down,而不会执行action_move和action_up)
ViewGroup的dispatchTouchEvent()
- 当你点击了任何控件,首先会去调用该控件所在布局的dispatchTouchEvent(),然后找到所在布局dispatchTouchEvent()中找到相应被点击的控件,在调用相应控件的dispatchTouchEvent().
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
//这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法
//来改变disallowIntercept的值
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//这里是ACTION_DOWN的处理逻辑
if (action == MotionEvent.ACTION_DOWN) {
//清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null
if (mMotionTarget != null) {
mMotionTarget = null;
}
//disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//遍历其子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
//如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才
//可以接受到Touch事件
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
//获取子View的位置范围
child.getHitRect(frame);
//如Touch到屏幕上的点在该子View上面
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//调用该子View的dispatchTouchEvent()方法
if (child.dispatchTouchEvent(ev)) {
// 如果child.dispatchTouchEvent(ev)返回true表示
//该事件被消费了,设置mMotionTarget为该子View
mMotionTarget = child;
//直接返回true
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
//判断是否为ACTION_UP或者ACTION_CANCEL
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
//如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false
//假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true
//当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false
//所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
//mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的
//dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法
if (target == null) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
//这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE
//ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的
//Touch事件, 将ACTION_CANCEL派发给target,然后直接返回true
//表示消费了此Touch事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
// clear the target
mMotionTarget = null;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
//如果没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target
return target.dispatchTouchEvent(ev);
}
整个ViewGroup的事件分发流程:
Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件