Android事件分发机制,浅谈解析

通过简单的demo来解析事件分发机制,主要通过简单的三层Activity–>ViewGrop–>View来分析事件分发机制。先通过一张简单的事件分发流程图来看看事件分发的基本流程。
事件分发的简单流程图
Activity与View是没有onInterceptTouchEvent这个方法的,也就是说这两者没法使用该方法拦截事件,我们先按照上面流程图来建一个demo,看看事件分发是否与我们流程图所画的一样。如下图:
在这里插入图片描述

在没有拦截任何事件的情况下可以看到打印的日志,从Activity开始一层一层的把事件传递给最底层的View,最底层的View的onTouchEvent()没有消耗事件的情况下,又向上依次按照层级去触发onTouchEvent()(在没有消费事件的情况下,也就是返回true),最终还是传递给了最上的Activity的onTouchEvent()来处理事件。然后我们来依次验证其它流程
验证dispatchTouchEvent
  1. ViewGroup 的dispatchTouchEvent() 返回true,其它方法返回值不变保持super看看事件流程
    在这里插入图片描述
    可以看到dispatchTouchEvent()返回true,事件就直接结束了不会往下传递。Activity或者View的也是一样的,可以自行验证。
  2. ViewGroup 的dispatchTouchEvent() 返回false或者View的dispatchTouchEvent()返回false的日志图分别如下在这里插入图片描述在这里插入图片描述可以看到两者的日志都如流程图所示,如果返回的是false则会像上一级的onTouchEvent传递。(ps:细心的可能会看到上面的图的日志打印,在日志最后总会多出几个不像流程图里所画的日志,比如MainActivity-----------dispatchTouchEvent(),MainActivity-----------onTouchEvent() 可能会问不是到onTouchEvent()就为止了?这里就需要从源码解释了,由于篇幅问题这里暂时不作详细说明,可以自己去查看源码,事件分发,当你阻止他继续分发后,自身这一层没有消费掉这个事件,还往上层传递的话,下一次判断就直接忽略了,所以直接就是最上层的dispatchTouchEvent()后,就直接onTouchEvent,因为ViewGroup或View的dispatchTouchEvent返回false,没法传递事件。 除非,你抬起手指在点击一次,它才会重新来开始判断,有兴趣的可以从源码分析,这里我截图都是截所有打印的日志)
dispatchTouchEvent()的总结
  1. 当dispatchTouchEvent()返回false,不像下分发事件时,无论是ViewGroup还是View都会向它的上一层的onTouchEvent()传递(会逆向向上传递),不会执行自身的onTouchEvent()。Activity的则会直接事件结束。
  2. 当都是默认的super. 则事件将会继续向下分发,直到事件被消费为止。
  3. 当返回true时,表示事件直接被消费,这个事件也就停止分发且不会逆向向上传递,直接结束了。
验证onInterceptTouchEvent()

1.ViewGroup的onInterceptTouchEvent()返回true,其它方法保持初始状态值
在这里插入图片描述
2.ViewGroup的onInterceptTouchEvent()返回false,或者super.onInterceptTouchEvent(ev),则事件会依次向下传递直到被消费为止,就跟初始化的日志图是一样的,这里就不上图了。

onInterceptTouchEvent()的总结

onInterceptTouchEvent()是ViewGroup特有的方法,View和Activity中没有这个方法,当返回false/super时事件将会正常向下分发,分发至下级的dispatchTouchEvent方法;返回true时,则表示ViewGroup容器拦截后续事件,会执行该ViewGroup的onTouchEvent()方法,如果自身onTouchEvent()没有消费掉该事件,则会通过onTouchEvent()向上传递,直到事件被消费

验证onTouchEvent()

1.ViewGroup的onTouchEvent()返回true,自身消耗掉该事件,其它方法不拦截也不消耗事件在这里插入图片描述
2.View的onTouchEvent()返回true
在这里插入图片描述
3.ViewGroup或者View的onTouchEvent()返回false,其它保持原样,则日志输出图如正常流程
(ps:这红色框框框起来的部分呢,上面几个图也是,忘记框起来了,就如前面的ps所说。最终消费掉该事件的在哪一层,下次判断就会省去那些没有意义的判断,比如第一个框框图,虽然第一次判断走到了View的onTouchEvent,但是它没有消耗掉该事件,而是向上传递给了ViewGroup的onTouchEvent()消费掉了,源码里面的的判断下一次就会直接省去View里面的判断,直接就是Activity–>Group。也可以这样说就是一个事件一旦交给一个View处理,那么它就必须消耗处理掉,否则同一事件序列中剩下的事件就不再交给它来处理了,短时间内。这里需要自己看源码分析,所以我这里的截图,没必要纠结红色框里面的打印周期)

onTouchEvent()的总结

返回true则立即消费掉事件,事件将不会向上传递,事件到此终止。返回false/super则不消费掉此次事件,事件将会层层向上传递,直到被消费。

总结一下事件分发中这几个方法的返回不同值时的表现
  1. dispatchTouchEvent和onTouchEvent这两个方法呢无论是Activity,ViewGroup还是View,只要返回了true就表示,该事件到此就终止了,不会往下或者上传递事件了,什么都结束了。
  2. dispatchTouchEvent和onTouchEvent这两个方法返回false/super时呢,对于ViewGroup或者View来说,事件都回传给父控件的onTouchEvent处理。如果是Activity呢就直接结束了,因为它是最上层了到此就终止了。
  3. onInterceptTouchEvent方法默认是不会去拦截事件的,因为子View也需要这个事件,所以onInterceptTouchEvent()拦截器返回 super.onInterceptTouchEvent() 和 false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent()传递;如果需要拦截呢就要返回true。
    (ps:最好自己根据上面的流程图写代码跟踪一下日志,就清晰了)
关于onTouch(),onClick()的优先级和影响

给view设置setOnTouchListener和setOnClickListener事件,其它方法默认super,初始化看一下日志输出

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i("TAG","EventView-----------OnTouchListener()");
                return false;
            }
        });

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("TAG","EventView-----------OnClickListener()");
            }
        });

在这里插入图片描述
在默认都不拦截的情况下,onTouch的优先级高于onTouchEvent,onTouchEvent高于onClick.
我们将onTouch返回值改为true看看
在这里插入图片描述
可以看到onTouch返回true以后View的包含onTouchEvent()事件的后面方法都不会执行了。被onTouch()消耗完了。onClick()是优先级最低的。在源码里面我们可以看到,这里贴上的是View里面的部分源码,ViewGroup的也差不多。在dispatchTouchEvent()的方法中

 public boolean dispatchTouchEvent(MotionEvent event) {
        ......

        boolean result = false;//是否消耗事件
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //若设置了OnTouchListener,则先调用onTouch()。
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            //若onTouch()没有消耗事件则调用onTouchEvent()
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ......
        return result;
    }

onTouchEvent()中则包含onClick的代码块事件中的performClickInternal()就能找到点击事件

 public boolean onTouchEvent(MotionEvent event) {
        ......
       if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
              // This is a tap, so remove the longpress check
              removeLongPressCallback();

        // Only perform take click actions if we were in the pressed state
        if (!focusTaken) {
            // Use a Runnable and post this rather than calling
            // performClick directly. This lets other visual state
            // of the view update before click actions start.
            if (mPerformClick == null) {
                mPerformClick = new PerformClick();
            }
            if (!post(mPerformClick)) {
                performClickInternal();
            }
        }
    }
 }

可以看到mOnTouchListener.onTouch的优先级是高于onTouchEvent的。

onClick(),setOnClickListener()时,如果该View的onTouchEvent()不是super.onTouchEvent(event)时,无论设置是return false还是true该事件不会响应。如下图
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("TAG","EventView-----------onTouchEvent()");
//        return super.onTouchEvent(event);
        return false;
    }

设置false
在这里插入图片描述
设置true
在这里插入图片描述
只有当View的onTouchEvent()返回super.onTouchEvent(event)时,setOnClickListener()才会生效,默认为消费了该事件,不会往上在传递给ViewGroup的onTouchEvent()处理。
在这里插入图片描述

ViewGroup中的requestDisallowInterceptTouchEvent() 设置是否允许拦截

在ViewGroup中如果在onInterceptTouchEvent()中拦截了事件,但是子View在某些情况下又需要该事件怎么办?在ViewGroup的dispatchTouchEvent()的源码中我们可以发现disallowIntercept 设置是否拦截,跟随这个我们可以看到requestDisallowInterceptTouchEvent()设置为true的时候,可以不拦截onInterceptTouchEvent()

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            ......
             // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
          ......
    }
    
 @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

我们修改一下EventView和EventGroup中的代码,使其在滑动的时候拦截事件Group自己消耗
EventView

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
//                getParent().requestDisallowInterceptTouchEvent(true);
                Log.i("TAG", "You down EventView");
                break;
            case MotionEvent.ACTION_UP:
                Log.i("TAG", "You up EventView");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("TAG", "You move EventView");
        }
        return true;
    }

EventGroup

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return false;
            case MotionEvent.ACTION_MOVE:   //表示父类需要
                return true;
            case MotionEvent.ACTION_UP:
                return true;
            default:
                break;
        }
        return false;    //如果设置拦截,除了down,其他都是父类处理
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }

由于我们EventGroup在滑动的时候,return true自己消耗了事件,则EventView不会响应事件输出日志
在这里插入图片描述
但是我们要在滑动的时候EventView也要响应自己的事件,把上面我们注释的代码块解除看看日志

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                Log.i("TAG", "You down EventView");
                break;
            case MotionEvent.ACTION_UP:
                Log.i("TAG", "You up EventView");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("TAG", "You move EventView");
        }
        return true;
    }

在这里插入图片描述
很明显getParent().requestDisallowInterceptTouchEvent(true); 这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。也就是说子元素能够通过调用requestDisallowIntercept(boolean b)来控制父容器能否调用onInterceptTouchEvent(),是否进行事件拦截。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#前言 之前笔者其实已经写过事件分发机制的文章:[快速理解android事件传递拦截机制概念](http://blog.csdn.net/double2hao/article/details/51541061) 但是,现在看来其实更像是一篇知识概括,多出可能未讲清楚,于是打算重写事件分发,用一篇文章大致讲清楚。 首先,形式上笔者最先思考的是使用源码,此者能从原理上讲解分发机制,比起侃侃而谈好得多。但是源码的复杂往往会让新手产生畏惧难以理解,于是笔者最终还是打算使用实例log来让读者理解android事件分发。 #重要函数 笔者此次主要提及最常用的几个函数: (其间区别看源码很容易理解,此处直接给上结果) **onClick():**这个函数是是View提供给我们的OnClickListener这个接口中的函数,在这里可以自定义对点击事件的处理逻辑。会在onTouchEvent()中进行调用。 **onTouch():**这个函数是View提供给我们的OnTouchListener这个接口中的函数,在这里面可以自定义对触摸事件的处理逻辑。 **onTouchEvent():**这个函数是view内部的触摸事件的处理方式,其间包括获取焦点,调用onClick()等等。 **dispatchTouchEvent():**这个是View的事件分发函数,在ViewGroup中进行重写。在View中其间会调用onTouchEvent(),在ViewGroup中其间会调用onInterceptTouchEvent()和onTouchEvent()。 **onInterceptTouchEvent():**这个函数是事件拦截函数,是ViewGroup才有的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值