安卓学习Android自定义View嵌牛IT观察

安卓view的事件分发机制

2019-02-25  本文已影响12人  小怪兽大作战

何为view的事件分发

安卓的事件分发是指点击事件(MotionEvent)在view树中的传递过程。这句话里面有几个概念1.点击事件,2.view树。

点击事件

所谓点击事件,安卓系统捕捉手指在屏幕上点击的动作,并封装成MotionEvent这个对象。分发的就是这个MotionEvent对象。一个完整的点击事件,由一个按下事件(ACTION_DOWN),若干个滑动事件(ACTION_MOVE)和一个手指从屏幕上松开的事件ACTION_UP)组成。
MotionEvent中携带了一个事件对象的所有信息,包括事件类型(MotioneEvent.getAction),相对于父view的点击坐标(getX,getY),相对于屏幕的点击坐标(getRawX(),getRawY())。

view树

安卓中的view个groupView构成了一颗树形结构。最外层是docor view(也就是我们平时在onCreate中使用setContentView中的父容器),然后是我们在docor view中自定义的子view(也就是我们在xml中自定义的view)。groupView内可以包含很多和子view,同时groupView本身也是一个view,所以这样就形成了一个view树,如下图所示。


image.png

事件分发中的几个关键函数

dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。
groupView中这三个函数都有,view中只有dispatchTouchEvent和onTouchEvent

dispatchTouchEvent

如果事件传递到当前的view,则该view的dispatchTouchEvent一定会调用。
在viewGroup中,dispatchToEvent首先会调用onInterceptTouchEvent判断viewGroup本身是否拦截事件。如果viewGroup本身拦截事件,则会调用viewgroup的onTouchEvent处理事件;否则,会遍历该viewGroup的所有子view,调用子view的dispatchToEvent,将事件传递到子view中。
如果事件传递到一个普通的view(不是viewGroup),那么在该view的dispatchTouchEvent方法中会直接调用该view的onTouchEvent。如果onTouchEvent返回false,表示该view不消耗该事件,则以后该事件序列的其他事件不会再传递到该view中,如果onTouchEvent返回true,表示该view消耗事件,则该view的父viewGroup会保存该view的实例,下次将该事件序列的其他事件直接发送到该view。
如果viewGroup本身不拦截事件,而他的所有子view也不消耗事件,则viewgroup的onTouchEvent会被调用。同理如果viewgroup的父viewGroup的所有子view(也就是viewGroup的兄弟view)都不消耗事件,viewGroup的父viewGroup的onTouchEvent会被调用。这样,事件又会一级一级地上传。

onTouchEvent

在dispatchTouchEvent中调用,用来处理点击事件。如果返回false,则不会消耗该事件,返回false,表示消耗该事件。

onInterceptTouchEvent

在viewGroup中调用,用来判断是否拦截事件,如果拦截某个事件,该事件不会传递到子view,并且在该事件序列的其他事件中该方法不会调用。

事件分发的大致过程如下图所示


image.png

viewGroup的dispatchTouchEvent的伪代码如下

View mTarget=null;//保存捕获Touch事件处理的View
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        if(ev.getAction()==KeyEvent.ACTION_DOWN){
            //每次Down事件,都置为Null

            if(!onInterceptTouchEvent()){   //如果viewgroup不拦截事件
            mTarget=null;
            View[] views=getChildView();
            for(int i=0;i<views.length;i++){   //遍历所有子view,调用子view的dispatchEvent
                if(views[i].dispatchTouchEvent(ev))   //如果某个子view消耗事件,保存该子view的实例,返回true
                    mTarget=views[i];
                    return true;
            }
          }
        }
        if(mTarget==null){   //如果所有的子view都不消耗事件
              return onTouchEvent(ev);   //调用viewGroup的onTouchEvent
        }
        if(onInterceptTouchEvent(ev)){   //
                return onTouchEvent(ev);   //调用viewGroup的onTouchEvent
         }
    }

总结

1.同一个事件序列是从手指触屏幕开始,到手指离开屏幕那一刻。中间包括了一个ACTION_DOWN事件,若干个ACTION_MOVE事件,和一个ACTION_UP事件。
2.正常情况下,一个事件序列只能被一个view拦截且消耗。如果一个view消耗了ACTION_DOWN,则该事件序列的所有事件都会直接发送到该view中。
3.如果一个view不消耗ACTION_DOWM,则该事件序列的其他事件都不会传递到该view中
4.viewgroup默认不拦截任何事件
5.view的onTouchEvent默认消耗事件,除非他是不可点击(既不clickable也不longClickable)
6.view的enable不会影响onTouchEvent,一个view即使是disable的,只要他可以点击,就可以消耗事件
7.点击坐标不在view的范围内,点击事件不会传递到该view中。
8.事件从上到下的传递过程 activity->window->docor view ->viewgroup ->view
9.view的onTouchListener的优先级大于onTouchEvent。如果onTouchListener返回true,则onTouchEvent不会被调用。onTouchEvent中会调用onClickListener。

上一篇下一篇

猜你喜欢

热点阅读