android View 事件分发

作者: 草丛伦 | 来源:发表于2017-05-22 17:19 被阅读126次

    关于事件分发,其实看了很多资料,看的时候都是看得懂,但是实际运用于事件冲突的时候,却不知道如何下手,希望能写完这篇文章跟事件冲突的文章后,能有所进步。
    先上代码 ,很实用的伪代码

    //伪代码
    public void dispatchTouchEvent(MotionEvent ev){
        boolean consume = false ;//用这个变量 实现单通道出口
        if(interceptTouchEvent(ev)){//拦截方法 默认是不拦截的
            consume = onTouchEvent(ev);
        }else{
            consume = child.dispatchTouchEvent(ev);//父类如果不拦截 就会将事件传递到子类去,这样一直传下去 ,直到子类处理 返回true  ,如果一直不处理即返回false ,那又会逐级上传
        }
        return consume ; //如果为true 表示这个事件被当前的view所消费了 
    }
    

    MotionEvent 点击事件对象 三个重点方法 dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent()

    dispatchTouchEvent(): 
    用于传递事件 ,如果一个view能接收到事件,那它的这个方法必定会被触发,可以理解为这是view的事件入口
    ,返回值代表着是否消耗了这个事件。
    
    onInterceptTouchEvent():
    用于拦截事件,只有viewgroup中才有这个方法,另外 如果这个view拦截了这个事件,那么在接下来的事件当中,
    他不会被触发,拦截后消费掉这个事件,那之后的方法也会直接跳到这个view头上,
    然而拦截后却不消费掉这个方法(即onTuchEvent()方法返回false),
    那么当前的view是无法再接收到事件了,之后的事件会跳转到父view去。
     具体的内容,可以看代码。
    
    onTouchEvent():
     用来处理点击事件的,只要事件被拦截 或者本身是view,那么就会触发这个事件,
    如果返回true ,就是真正的消费了事件,一般dispatchTouchEvent的返回值 就是这个方法的返回值。
    如果用户对view设置了onTouchListener ,那么会调用onTouch方法 ,
    这个方法的优先级会比onTouchEvent的方法高。如果onTouch返回true,表示事件被消费了,
    onTouchEvent直接不会被调用,当返回false的时候 后者才会被正常的调用起来。
    而onTouchEvent方法中会有判断是否设置了onClickListener ,设置了的话 onClick会被调用。
    
    简单的来说 优先级 onTouch> onTouchEvent>onClick
    

    点击事件的传递路径 :Activity —>Window ——>View 关于Window 跟DecorView 回头再慢慢说。 如果下面的对象都没有消费这个事件的话,又会逐级的上传,这个过程中,如果还是没有人消费这个事件,最终会调用Activity的onTouchEvent方法。
    文中总结的结论 挑重点复述:
    一个系列事件是以down事件开始,中间穿插着不定量的move事件,最后以up方法结尾
    正常情况下,一个事件序列只能被一个view拦截,正常的情况他是拦截后,会把后续的事件都传给这个view处理,当然也可以在onTouchEvent方法中直接将方法强制传给其它view处理(有个疑问:父类子类都可以吗?)。
    View拦截事件后,他的onInterceptTouchEvent不会被重复调用,后续的事件会直接传递到他身上。
    如果一个view要处理事件,那么从down方法开始 就必须返回true,否者后续的事件会被交给父类去处理。如果down方法返回true,后续的事件返回false,那么这个方法会直接消失,并不会交给父类去做,并且后续的事件依然存在,依然会传递过来,最后这些消失的事件会统一交给activity处理。
    Viewgroup默认不拦截事件,即onInterceptTouchEvent方法默认返回false,所以有需求的时候需要自己重写这个方法。
    View没有onInterceptTouchEvent方法。
    View的onTouchEvent这个方法默认返回true,除非他是不可点击的(clickable or longClickable都为false),前者根据控件确认值,而后者默认都是false。
    enable这个属性不会影响onTouchEvent的值,因为这个方法只会去判断clickable跟longClickable 是否为false,这两者只要有一个为true,这个方法就会被正常的进行,返回true。
    onClick发生的前提 是这个view是可点击的 ,并且他收到了down跟up方法,这其实也说明这个方法是在up之后才会被触发。(当你设置了onClickListener接口后,会自动就将clickable设置为true,)
    事件传递都是由外向内的,都是从父元素慢慢的分发传递到子元素去的,但是子view中可以调用requestDisallowTouchEvent方法可以干预父元素的事件传递,但是down事件除外。(因为down事件的时候会直接重置这个request状态,所以即使你设置了, 也是无效的)。

    如上所说的,一个事件最初是从activity开始的,他会在dispatchTouchEvent方法中调用他的内部Window,Window会将事件传递给DecorView,这个就是setContentView中的view的父容器,这个存在可以通过 Activity.getWindow().getDecorView()方法获取

    //Activity中
    public boolean dispatchTouchEvent(MotionEvent ev){
        if(ev.getAction()==MotionEvent.ACTION_DOWN ){
            onUseInteraction();//???
        }
        if(getWindow().superDispatchTouchEvent(ev)){//同上,调用window的方法 逐级传递 
            //如果条件成立 所以里面的存在把事件给消费了 ,那么就直接返回true
            return true ;
        }
        return onTouchEvent(ev);//子元素没有一个消费掉了这个方法,所以会调用activity的onTouchEvent
    }
    //PhoneWindow的superDispatchTouchEvent 
    public boolean superDispatchTouchEvent (MotionEvent ev){
        return mDecor.superDispatchTouchEvent(ev);//直接调用DecorView的方法 根本没有做其它多余的操作 ,就是传递事件
    }
    

    关于window就不详说了,是个抽象类,他的实现类是PhoneWindow
    我们之前在setContentView中设置的view ,除了自己用id来找以外,还可以通过这个mDecorView获取 ((ViewGroup)getWindow().getDecorView().findViewById(Android.R.id.content)).getChildAt(0); 不过这么写真的麻烦 ,只是说明一下而已。 DecorView继承FrameLayout ,另外 事件传递的过程中,除非事件被拦截,不然onTouchListener不起作用。 所以说 想做事件传递分发的时候,基本都是重写 Viewgroup的dispathchTouchEvent方法

    //viewgroup的  dispatchTouchEvent的方法片段
    final boolean intercepted ;
    //为down 或者后者不为null  后者是否为null取决于是否有子元素处理了事件,如果有处理,那么这个对象就会指向子元素
    if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget !=null){
        //判断子类有没使用requestDisallowTouchEvent方法把 猜猜应该是设置了之后返回的是false 那么父类就不拦截了
        final boolean disallowIntercept  = (mGroupFlags &FLAG_DISALLOW_INTERCEPT )!=0;
        //能作用除down以外的其它动作,如果是down的话 ,viewgroup还是会调用intercept方法判断是否拦截的
        if(!disallowIntecept){
            intecepted = onInterceptTouchEvent(ev);
            ev.setAction(action);//防止动作被改变 所以重置它
        }else{
            //不拦截 子类才能搞事情 是吗? 
            intercepted = false ;
        }
    }else{
        //不是down事件  子view也没处理事件 那么就会直接跳到这里来 并没有经过onInterceptTouchEvent
        intercepted = false ;
    
    }
    //如上代码表明  onInterceptTouchEvent不是每次都会被调用的
    
    这里写图片描述

    view里面的方法 ,看的出来 没有intercept方法,并且onTouch方法优先级很高


    这里写图片描述

    看view的onTouchEvent方法中 ,看出 可用不可用 都不影响view消费掉这个事件,如果它不可用,他会判断他的clickable 跟longclickable方法 哪个为true 只要为true 就能玩 还有其它的一些代码也证明了这一点。 //关于setClickListener 跟 setLongClcikListener 只要设置了这个监听 ,那两个状态就自动会被修改。

    相关文章

      网友评论

        本文标题: android View 事件分发

        本文链接:https://www.haomeiwen.com/subject/sjhixxtx.html