关于Android的Touch事件的分发机制

首先,有三篇非常好的文章,值得一看。
Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
在此基础上,我自己又添加了一点理解,我是小白,可能有错,有人看的话直接指出来,别客气。
view的dispatchTouchEvent上面文章说得很清楚了,迷惑人的是当有好多层viewgroup的时候事件的分发。所以我就主要关心了事件到底找到谁来执行。比如,现在我弄了个tabhost中有viewpager,listview,还能滑动切换tab,右划出menu,结果往往彼此冲突!


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;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        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;
            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)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        //A点,这一句将会层层调用,最后到达ACTION_DOWN区域最底层的那个view
                         // 若是返回了true,那么将会层层往上返回true
                         //若返回了false,将会执行到B点
                         //若果看完所有分析再回到这里,你会发现,原来片头所列文章中
                         //的为什么动作是一系列的,
                         //又为什么返回false和返回true那么难理解,
                         //button可以执行到upimageview,不行,
                         //其实这就是一个关键就在这里和B点,
                         //只有成为taget的才能分发到下一个动作!
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
     //判断是否为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;
    }
    final View target = mMotionTarget;
    //以下几句最后再看
    //E点,最关键的时刻到了,如果一直分发下去,最后一个target代表的viewgroup
    //(上一个拦截的vieroup)没有target的!
//调用父元素的super.dispatchTouchEvent()。
     // 即view的dispatchTouchEvent()
     // 现在就可以去讨论view的dispatchTouchEvent()方法了
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
        //B点,由于Child没有将ACTION_DOWN消费,自己看是否消费,
         // 若返回true,则上一级viewgroup执行到A点就层层往上返回true,
         // 返回false,上一级viewgroup向下执行至此
         // 以此循环直到返回到根节点
         // 这里很关键的是直接给出了此viewgroup的dispatchTouchEvent(ev)的返回;
         // 也就是说,若是ACTION_DOWN没能找到target给mMotionTarget赋值,其他动作都不能被分发!
         // target的意义就在于此了,
         // 正因为如此在每一个ACtionDown开始时先mMotionTarget=null

       return super.dispatchTouchEvent(ev);
    }
    //由上分析可见,到此为止,一定找到了target,接下来的代码才有意义, 
     // 而ACTION_DOWN的所有判断和执行都已经完成,若没有这个
     // 这个target一定是当前viewgroup的子view

     //下面就是move和up了

     //还有这里最多执行到倒数第二层,最下面的view的执行都是在C点和D点
      //但是我们发现viewgroup先执行onInterceptTouchEvent(ev)然后执行C和D
       // 也就是说我们可以同时在child和parent中进行操作,

       // 很简单,拦截就到C,不拦截就到D
    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);
        //C点,当拦截move或up时,在这里调用target的dispatchTouchEvent(ev),但只是执行一次
         // 无论返回什么,这个viewgroup都返回true,在上一级viewgroup中
         // 即此viewgroup消费了此touch
         // 调用一次target.dispatchTouchEvent(ev),执行的动作为取消!!!
        if (!target.dispatchTouchEvent(ev)) {
        }
        //一旦拦截将 mMotionTarget = null,也就是说当下一个动作将执行final View target = mMotionTarget;
         // 到if (target == null) 从而调用view的dispatchTouchEvent();
         // 一定要记住的是move往往是一串!!!拦截后
         //当前这个viewgroup就成为最后一个target,
         //自己的target=null,将执行E点的parent.dispatchtouchEvent(ev)
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    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;
    }

    //D点,在此viewgroup不拦截的情况下,这一句是实际的处理move和up

     //当我们的target!=null,这里就会层层向下分发
     //直到taaget=null,执行E点
    return target.dispatchTouchEvent(ev);
}

从上面我们已经可以看见,关键点就是ABCDE五处,ACTION_DOWN在AB两处找到target,C处的拦截会改变target(比如你在listview的所在viewgroup做move拦截,你会发现,好多点击都不灵了!,因为你手抖了,down找到的item不在是target,而单击的执行在up,结果没有效了)D处将把非ACTION_DOWN的事件层层向下发,直到没有target的那一个viewGROUP,这个在这里调用VIEW的dispatchTouchEvent(ev)方法。
至于怎么执行看viewgroup是否重写了view的ontouch()和onTouchEvent()方法。(重写这个是为了实现自定义的动作)
而往往我们注册的listener中就重写了这些方法,像listview(其实是abslistview)也重写这些方法。我们自定义view的时候也会重写他们。
而onInterceptTouchEvent(ev)的重写将直接关系到事件能不能向下分发下去!(重写这个是为了区分谁来消费动作)
其实这样想down-up,就是个点击,往往是直接到了最小的那个view,这正是我们想了,但down-move-up的时候我们一定是想在move的时候做判断,拦截。这样就能实现各种效果了。
也就是重写onInterceptTouchEvent(ev)和requestDisallowInterceptTouchEvent(boolen)。
至于怎么在move的时候拦截我还在学习中,我是初学,若有理解不到位或错误请指出。
现在我弄了个tabhost中有viewpager,listview,还能滑动切换tab,右划出menu,结果往往彼此冲突!
继续和这个纠缠。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值