虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > Android编程 > Android Touch事件传递机制详解 上

Android Touch事件传递机制详解 上
类别:Android编程   作者:码皇   来源:互联网   点击:

最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘记了,其实网上关于T

 

最近总是遇到关于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方法,对于应用程序窗口 //这两个条件一般是满足的 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的这个方法非常简单,首先判断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事件的。如果哪里没有写清楚的 ,欢迎拍砖。。。


 

相关热词搜索: 机制 事件