android事件分发(一)

事件分发在android中非常重要,写了3篇文章总结其中的故事

android事件分发(一)

android事件分发(二)源码源码

android事件分发(三)重要的函数requestDisallowInterceptTouchEvent


概述

1、在父控件的onTouch里返回true,子控件就无法收到点击事件,对吗? 这是错误的,阻止事件往下分发得用onInterceptTouchEvent

2、事件传递由父控件传递到子控件,事件消费是子控件优先,子控件不消费就传递给父控件消费

3、子控件是clickable的,但是没有写onclick事件,但是父控件有onclick事件,那么点击子控件,父控件会响应吗?

不会,在onTouchEvent内部,只要控件是clickable或者longcickable的,那就会返回true,意味着子控件消费了事件,不会传递给父控件,这个时候如果想要子控件的点击触发父控件的事件,只能添加子控件的onClick事件,在onClick内部执行与父控件一致的操作

源码分析

触摸了任何一个ViewGroup都会调用ViewGroup的dispatchTouchEvent,先进入onInterceptTouchEvent,如果返回true的话,就拦截了,交由本viewgroup的View::dispatchTouchEvent方法,注意这里和前面的dispatchTouchEvent方法不一样,一个是view的dispatchTouchEvent(后者),一个是viewgroup的dispatchTouchEvent(前者)。如果onInterceptTouchEvent返回false,那就不拦截事件。

找本viewgroup的子控件,找到那个包含点击位置的子控件,交由子控件的dispatchTouchEvent处理。如果子控件的dispatchTouchEvent返回true的话,那就直接return true,如果子控件的dispatchTouchEvent返回false的话,那就交由本viewgroup的View::dispatchTouchEvent方法
如果没找到包含点击位置的子控件的话,就交由本viewgroup的View::dispatchTouchEvent方法

可见最后的终点要么返回true,要么交由本viewgroup的View::dispatchTouchEvent方法。View::dispatchTouchEvent的执行流程在android点击事件(View)说过。一般来说,View::dispatchTouchEvent都是调用OnTouchEvent

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;
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    if (target == null) {
        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 (!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)) {
        }
        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;
    }
    return target.dispatchTouchEvent(ev);
}

这个方法代码比较长,我们只挑重点看。首先在第13行可以看到一个条件判断,如果 disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。

如果ViewGroup不拦截事件,就会交给子控件处理,进入13行的条件内。然后在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View。

如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent,之后的流程就和  Android事件分发机制完全解析,带你从源码的角度彻底理解(上)  中讲解的是一样的了。调用子View的dispatchTouchEvent后是有返回值的。我们已经知道,如果一个控件是可点击的,那么点击该控件时,dispatchTouchEvent的返回值必定是true。因此会导致第29行的条件判断成立,于是在第31行给ViewGroup的dispatchTouchEvent方法直接返回了true。这样就导致后面的代码无法执行到了。

如果子控件没有消费掉这个事件的话,就会走到38行。我们继续往后看,在第44行,如果target等于null,就会进入到该条件判断内部,这里一般情况下target都会是null,因此会在第50行调用super.dispatchTouchEvent(ev)。这句代码会调用到哪里呢?当然是View中的dispatchTouchEvent方法了,因为ViewGroup的父类就是View。之后的处理逻辑又和前面所说的是一样的了。之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。

流程图

一个是阻断子控件消息,一个是子控件处理,一个是子控件不处理

onInterceptTouchEvent

SDK给出的说明如下:

·  You will receive the down event here.

·  The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.

·  For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().

·  If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.

 各种情况总结

总结一下,首先定义down,move,move....,up为一系列动作,从手按下到手放开,基本的规则是:

一般来说我们定义一个viewgroup,不会重写 dispatchTouchEvent ,也不会设置onTouchListener,但是会重写onTouchEvent。
总结一下,事件处理的一些规律。
首先定义down,move,move....,up为一组事件,或者一个cycle(官方说法),从手按下到手放开。
我们从一个viewgroup的角度来分析下一组事件到来,会发生什么事?
可以看上边的流程图
我是viewgroup。
这里说的我的onTouchEvent严格意义上说,该是dispatchTouchEvent,只是dispatchTouchEvent一般都是调用onTouchEvent,所以我们可以简单的认为dispatchTouchEvent就是调用 onTouchEvent 。
基本的规则是:
1 down事件首先会传递到我的onInterceptTouchEvent()方法
2.0 onInterceptTouchEvent返回true我就会拦截事件,我的儿子们不可能收到这个事件,返回false就相当于神马都不干,把事件传递给子控件
2.1.0 down事件的onInterceptTouchEvent返回true,之后会调用我的view:dispatchTouchEvent(大部分情况其实就是onTouchEvent),如果我的onTouchEvent也返回true,那么之后的move事件和up事件都不会经过onInterceptTouchEvent,而是直接传递到onTouchEvent。简单的说,我在某刻拦下来了事件,那么后面的事件都会直接拦截,根本不再调用onInterceptTouchEvent。
2.1.1.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回false,那后面的move,up事件都不会执行。为什么?还记得吗?onTouchEvent返回了false,那view:dispatchTouchEvent就是返回了false,而一个cycle内的前一个action的dispatchTouchEvent返回了false,后面的action直接就丢弃了。
2.2.0 如果down的时候onInterceptTouchEvent返回false,然后某个move的onInterceptTouchEvent返回了true,move的onTouchEvent也返回了true,那么之后的move和up等事件都不会触发onInterceptTouchEvent,而是直接传递给onTouchEvent(注意这里所有的onInterceptTouchEvent和onTouchEvent都是指父视图内的)
2.2.1 如果down的时候onInterceptTouchEvent返回false,那就丢给了儿子处理,儿子处理在onTouchEvent里返回了true,表示他处理好了,那下一个action  move依然先传给我的onInterceptTouchEvent,我还可以拦截。以前有个错误的理解,我以为action给儿子了并且成功处理了之后,下一个action就直接给儿子了,其实不对,还是会先给父亲,父亲看看是否拦截,再给儿子。父亲比儿子霸道很多,父亲拦下了一个action,后面的action都默认拦下,但是父亲放过了一个action,下一个action来的时候,父亲还是会拦拦看,这其实就是某些滑动冲突的原因。

注意,这里说的onTouchEvent严格意义上说,该是dispatchTouchEvent,只是dispatchTouchEvent一般都是调用onTouchEvent

测试onInterceptTouchEvent

1、 parent 的onInterceptTouchEvent 为true,onTouchEvent为true

日志
parent onInterceptTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_MOVE
parent onTouchEvent ACTION_MOVE
parent onTouchEvent ACTION_UP


 2、 parent 的onInterceptTouchEvent 为true,onTouchEvent为false

日志
parent onInterceptTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_DOWN
   测试代码

public class MyLayout extends LinearLayout {

    public MyLayout(Context context) {
        super(context);
    }

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyLayout(Context context, AttributeSet attrs, int defStyleAttr)     {
        super(context, attrs, defStyleAttr);
    }



    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        String str=null;
        switch(event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                str="ACTION_MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:
                str="ACTION_CANCEL";
                break;
            case MotionEvent.ACTION_DOWN:
                str="ACTION_DOWN";
                break;
            case MotionEvent.ACTION_UP:
                str="ACTION_UP";
                break;
        }
        LogUtil.d("parent onInterceptTouchEvent " + str);
        return true;
//        return super.onInterceptHoverEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        String str=null;
        switch(event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                str="ACTION_MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:
                str="ACTION_CANCEL";
                break;
            case MotionEvent.ACTION_DOWN:
                str="ACTION_DOWN";
                break;
            case MotionEvent.ACTION_UP:
                str="ACTION_UP";
                break;
        }
        LogUtil.d("parent onTouchEvent " + str);
        return  false;
//        return super.onTouchEvent(event);
    }

}

public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        String str=null;
        switch(event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                str="ACTION_MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:
                str="ACTION_CANCEL";
                break;
            case MotionEvent.ACTION_DOWN:
                str="ACTION_DOWN";
                break;<a target=_blank href="http://blog.csdn.net/phelovhl/article/details/8989910">点击打开链接</a>
            case MotionEvent.ACTION_UP:
                str="ACTION_UP";
                break;
        }
            LogUtil.d("child onTouchEvent"+str);
        return super.onTouchEvent(event);
    }
}

ACTION_CANCEL

自定义控件的时候免不了要写ACTION_CANCEL的case,ACTION_CANCEL事件是收到前驱事件后,后续事件被父控件拦截的情况下产生,onTouchEvent的事件回传到父控件只会发生在ACTION_DOWN事件中。比如子收到down事件处理了,返回true,然后move事件被父拦截了,那么child会收到cancel事件,这是为了child能够取消某些操作,能够恢复一个正常状态。可以参考http://blog.csdn.net/phelovhl/article/details/8989910

参考文献

http://blog.csdn.net/ddna/article/details/5473293
http://blog.csdn.net/guolin_blog/article/details/9153747

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值