(4.1.40.3)android事件分发

概述

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的执行流程在http://www.aiuxian.com/article/p-2997516.html说过。一般来说,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为一系列动作,从手按下到手放开,基本的规则是:

1.down事件首先会传递到onInterceptTouchEvent()方法

2.onInterceptTouchEvent返回true就会拦截子控件的事件,返回false就相当于神马都不干,把事件传递给子控件

3.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回true,那么之后的move事件和up事件都不会经过onInterceptTouchEvent,而是直接传递到onTouchEvent。

4.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回false,那后面的move,up事件都不会执行

5.如果down的时候onInterceptTouchEvent返回false,然后某个move的onInterceptTouchEvent返回了true,move的onTouchEvent也返回了true,那么之后的move和up等事件都不会触发onInterceptTouchEvent,而是直接传递给onTouchEvent(注意这里所有的onInterceptTouchEvent和onTouchEvent都是指父视图内的)

注意,这里说的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;
            case MotionEvent.ACTION_UP:
                str="ACTION_UP";
                break;
        }
            LogUtil.d("child onTouchEvent"+str);
        return super.onTouchEvent(event);
    }
}



参考文献

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、付费专栏及课程。

余额充值