尊重原创:http://blog.csdn.net/yuanzeyao/article/details/37961997
最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘记了,其实网上关于Touch事件的传递的文章真的很多,但是很少有系统性的,都是写了一个简单的demo运行了一下,对于我们了解Android Touch事件基本上没有任何帮助。
今天我打算从源码的角度来分析一下Touch事件的传递机制。在了解Touch事件之前,最好了解下Android中窗口的创建过程,这样对于Android窗口的整体结构和事件的传递过程会了解更深。我就把事件的始点定在PhoneWindow中的DecorView吧,至于是谁把事件传递给DecorView的我们先不用去关心它。(如果想深入研究,请阅读我的另外一篇文章Android中按键事件传递机制)我们只需要知道它的上家是通过dispatchTouchEvent方法将事件分发给DecorView就行了,我进入到该方法瞧瞧究竟。
在阅读之前最好阅读Android窗口创建过程
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
//该Callback就是该DecorView附属的Activity,可以看我的另外一篇文章《Android中窗口的创建过程》
final Callback cb = getCallback();
//如果cb!=null && mFeatureId<0 就执行Activity中的dispatchTouchEvent方法,对于应用程序窗口 <span style="white-space:pre"> </span> //这两个条件一般是满足的
return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
.dispatchTouchEvent(ev);
}在DecorView中事件通过dispatchTouchEvent方法被分发到了Activity中,相信Activity对于每个Android开发者都不会陌生吧,那我们就进入Activity的dispatchTouchEvent方法中。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//getWindow返回什么?如果阅读过我的《Android中窗口创建过程》的都知道就是PhoneWindow,如果PhoneWindow中的superDispatchTouchEvent方法返回了true,
//那么该Touch事件就被PhoneWindow给消费掉了,不会再继续传递,如果返回false,那么就会执行Activity的onTouchEvent方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}进入PhoneWindow中的superDispatchTouchEvent方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor是一个DecorView类型变量
return mDecor.superDispatchTouchEvent(event);
}public boolean superDispatchTouchEvent(MotionEvent event) {
//直接调用父类的dispatchTouchEvent方法
return super.dispatchTouchEvent(event);
}
@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;
//可以通过requestDisallowInterceptTouchEvent方法来设置该变量的值,通常是false
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
//onInterceptTouchEvent在默认情况下是返回false的,所以这里通常是可以进去的
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
//遍历ViewGroup的孩子,如果触摸点在某一个子View中,则调用在子View的dispatchTouchEvent
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
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 ,如果这个子View 的dispatchTouchEvent返回true,那么意味着这个事件
//已经被这个子View消费了,不会继续传递
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
//对于一个Action_down事件,如果走到了这里,说明所有的子View 都没有消费掉这个事件,那么它就调用父类的
//的dispatchTouchEvnet方法,ViewGroup的父类就是View
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 have a target, see if we're allowed to and want to intercept its
// events
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)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// 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;
}
return target.dispatchTouchEvent(ev);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}对于任何一款Android应用,展现给用户最上面的通常就是一个View,如Button,ImageView等等,也就是说一些触摸事件最终都是传递给了这个控件,如果控件消费了这些事件,那么就停止传递了,如果没有消费,那么就交给控件所属ViewGroup的onTouchEvnet处理,我们就看看View的dispatchTouchEvent方法吧
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
//(A)
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//(B)
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) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
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();
}
//(C)
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
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();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//(D)
return true;
}
return false;
}Android Touch事件传递机制详解 上,布布扣,bubuko.com
原文:http://blog.csdn.net/yuanzeyao/article/details/37961997