尊重原创: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);    }

进入DecorView中的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(MotionEvent event) {//直接调用父类的dispatchTouchEvent方法            return super.dispatchTouchEvent(event);        }

走到这里我们先暂停一下,会看一下DecorView类的dispatchTouchEvent方法,假设callBack不为空,那么调用CallBack的dispatchTouchEvent方法,否则调用super.dispatchTouchEvent方法,可是在CallBack不为空的条件下最中也是调用到了super.dispatchTouchEvent方法,那么它的super是哪个那,我们继续往下看:
通过源代码我们能够看到DecorView是继承自FrameLayout。所以事件终于是传递到了FrameLayout的dispatchTouchEvent中,FrameLayout中的此方法是继承自ViewGroup的,我们直接到ViewGroup中查看此方法吧:


@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);    }

刚才在看ViewGroup的dispatchTouchEvent方法时,我们看到了一个方法onInterceptTouchEvent,这种方法是干什么的呢,我们先看看他都干了什么吧

public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;    }

发现里面就是返回了一个false, 通过方法名字我们就能够知道该方法的作用,是否阻止TouchEvent的传递,默认是false 也就是不会阻止。

如今总结一下ViewGroup的dispatchTouchEvnet的逻辑 ,毕竟这种方法有些复杂:
1、假设disallowIntercept|| !onInterceptTouchEvent(),那么事件才干够继续传递下去,否则直接调用该ViewGroup的父类的dispatchTouchEvent,也就是View的dispatchTouchEvent.
2、依次遍历ViewGroup的全部子View,将事件传递个子View,假设某一个子View处理了该事件,而且返回true,那么事件结束,停止传递
3、假设全部的子View没有消费掉这个事件,那么就调用View的dispatchTouchEvent

对于不论什么一款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);    }

View的这种方法很easy,首先推断mTouchListener是否为空,而且这个View是否Eneable,假设都满足,那么首先调用mOnTouchListener.onTouch方法,假设onTouch方法返回true,那么就是说这个View消费了该事件,直接返回true,假设onTouch返回false,那么就会调用onTouchEvnet方法,这个mOnTouchListener是什么?

 public void setOnTouchListener(OnTouchListener l) {        mOnTouchListener = l;    }

看了这个就明确了吧,就是我们通过setOnTouchListener赋值的,另外我们还须要注意一点就是这个onTouch是在onTouchEvent方法之前运行的哦。
最后我们就看看这个View的onTouchEvnet吧

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;    }

这种方法也是相当的复杂啊,可是我们没有必要每一行都看,我们仅仅须要挑重点看就Ok了。
请细看我标了 A B C D的四个地方,在A处,假设该View是Disable的,那么仅仅要该View是clickable或者longclickable的,那么这个事件就被该View消费掉了,返回true
在看B 和 D,两处,假设该View是clickable或者longclickable的,那么D出总是返回true,也就是说事件一直被消费,至于C处我主要是说明的是View的onClick事件是在ACTION_UP中触发的。

学习到这里,我又须要总结一下:
假设我们触摸的一个View是clickable或者longclickable的,那么这个事件肯定会被这个View消费掉(当然前提是你没有改写它所在ViewGroup的onInterceptTouchEvent方法,假设你改写此方法返回true,那么View是无法接收到这个事件的)

我们如今还要思考一个问题,假设这个View没有消费掉这个事件,这个事件终于抛向何方?
还记得前面我说过ViewGroup的dispatchTouchEvent方法吗,假设它的全部的子View没有处理掉该事件,那么调用的是父类View的dispatchTouchEvnet方法,从而运行到了该ViewGroup的onTouch和onTouchEvent方法。

那假设ViewGroup也没有处理该事件呢,这里就要分两种情况啦:
1、假设这个ViewGroup不是DecorView,也就是说他的父View就是一个普通的ViewGroup(如LinearLayout里面放置一个LinearLayout),那么和上面子View没有处理掉消息有点类似,调用父类的onTouch和onTouchEvent方法
2、假设这个ViewGroup就是DecorView,那么就调用到了Activity的onTouchEvnet方法(此时没有onTouch方法)。


今天就先写到这里吧,后面我回用一个简单的Demo和一个简单的滑动冲突问题在深入学习TouchEvnet事件的。假设哪里没有写清楚的 ,欢迎拍砖。。。



更多相关文章

  1. Android的线程使用来更新UI----View的几种更新方法(Thread、Hand
  2. android使用全局变量的两种方法
  3. android解析xml文档的各种方法
  4. android屏幕旋转时,Activity不重新调用onCreate的方法
  5. Android横竖屏切换的解决方法
  6. android 事件处理(三)
  7. Android 解决65535的限制(官网推荐方法)
  8. 最新下载 android 源码方法

随机推荐

  1. WhatsApp & Tasker for Android – Read
  2. Android(安卓)Zip文件解压缩代码
  3. Android permission denied for window t
  4. 我的android 第19天 - 上下文菜单
  5. android 禁用或开启四大组件setComponent
  6. Android WebView选择图片、发送图片
  7. Android下setLatestEventInfo警告、Handl
  8. Android 创建SQLite数据库(一)
  9. Android 软件盘弹出时把顶部局顶上去的解
  10. android Log.isLoggable方法的使用