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

来源:互联网 发布:剑三正太可爱捏脸数据 编辑:程序博客网 时间:2024/05/12 23:48

首先,有三篇非常好的文章,值得一看。
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,结果往往彼此冲突!
继续和这个纠缠。

0 0
原创粉丝点击