事件传递机制

方法

  • dispatchTouchEvent:分发事件。如果返回true,表示事件分发下去后被处理了;如果返回false,则表示分发下去后没有被任何View处理
  • onInterceptTouchEvent 拦截事件。如果返回true,表示拦截事件;如果返回false,则表示不拦截。这里拦截的是本来要传给子控件的事件,所以这个方法是ViewGroup独有的。
  • onTouchEvent 处理事件。如果返回true,则表示处理事件;如果返回false,则表示不处理事件。

关于事件传递机制的一些结论

  • activity没有onInterceptTouchEvent方法的,因为是事件源,拦截没有意义
  • view没有onInterceptTouchEvent方法,因为是叶子节点,拦截没有意义
  • viewgroup中onInterceptTouchEvent方法默认返回false(不拦截),除非重写该方法并返回true;
	//ViewGroup.java
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }
  • view的ontouchEvent默认都会消费事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false(true表示消费该事件,false表示不消费该事件)
  • 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子元素,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。

ViewGroup的dispatchTouchEvent方法的伪代码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if(onInterceptTouchEvent(ev)){//是否拦截,拦截就走当前控件的onTouchEvent方法
            consume = onTouchEvent(ev);
        }else{//不拦截,调用子控件分发方法;以此类推,直到事件被消费
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }

事件传递方向:由外到内,再由内到外,直到事件被消费
在这里插入图片描述
分发流程

  • 首先viewgroup在分发时候首先判断自己是否需要此事件,如果需要就在onInterceptTouchEvent返回true,对事件进行拦截,并调用当前viewgroup的onTouchEvent方法;
  • 如果不如需要此事件,就会调用子控件的分发,依次类推直到事件被消费;
  • 如果当前控件的onTouchEvent返回false,表示不消费当前事件,紧接着会调用父控件的onTouchEvent;
  • 如果当前控件的onTouchEvent返回true,表示消费当前事件,该事件终止
    在这里插入图片描述
    事件冲突解决
  • 外部拦截法(父控件根据业务逻辑处理,子view需要事件的时候就不拦截,交给子view处理)
	//父控件控制逻辑的伪代码如下
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;//默认不拦截
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(当前容器需要这个点击事件){
                    intercepted = true;
                }else{//当前容器不需要这个点击事件
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;
    }
  • 内部拦截法(子控件根据业务逻辑处理,需要事件的时候请求父控件不拦截事件requestDisallowInterceptTouchEvent,不需要事件的时候请求父控件去拦截事件)

1.子控件中通过dispatchTouchEvent控制拦截逻辑

	//子控件中控制逻辑的代码如下:
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);//请求父控件不拦截
                break;
            case MotionEvent.ACTION_MOVE:
                if(父控件需要该事件){
                    getParent().requestDisallowInterceptTouchEvent(false);//让父控件处理该事件
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

2.父控件不能拦截ACTION_DOWN事件,因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父控件拦截了ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }
    }

参考
《android开发艺术探索》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值