上一篇中,我说明了Android中Activity和View触摸事件的传递流程,现在我们来继续学习MotionEvent在View的容器ViewGroup中的传递过程。
首先,ViewGroup继承于View,我们在开发中经常使用的LinearLayout,RelativeLayout,FrameLayout等都继承于ViewGroup,而Button,TextView,ImageView等都继承于View,ViewGroup对View最基本的扩展的功能就是可以添加子View。
ViewGroup触摸事件的传递,我们要先了解onInterceptTouchEvent()方法,这个方法默认返回false,表示ViewGroup是否拦截触摸事件,即如果返回true,拦截触摸事件,则不会将任何触摸事件ev向下传递给它的子View,换一句话说,这个时候,ViewGroup就变成了我们上一篇说的View,完全走View的触摸事件传递流程,同样的我们来写代码验证。
我们自定义一个EventRelativeLayout 继承于RelativeLayout,重写onInterceptTouchEvent方法,返回true, 重写onTouchEvent方法,返回false,然后将我们Activity的根布局改成EventRelativeLayout,然后在onCreate中给它添加onTouchListener,
运行App,点击按钮,查看打印日志,
可以看到,我们的button没有接收到触摸事件,现在将onInterceptTouchEvent返回值改为false,再次运行App,点击按钮,查看打印日志,
很明显,我们的按钮的触摸事件和它的父容器的触摸事件,都触发了,这是因为,我们EventBtn的onTouchListener方法和onTouchEvent方法都返回false,没有消费触摸事件,事件会向上继续传递。那么如果ViewGroup不拦截触摸事件,事件在它的子Views中又是如何传递的呢?
触摸事件ev会按照子View加入ViewGroup先后顺序相反的顺序,依次有机会去消费此触摸事件ev,即最后加入的最先有机会消费此触摸事件,当然,它消费的前提是,触摸点的坐标在这个子View的frame范围之内,其实只需要判断触摸事件的Point是否在子控件的Rect范围之内。我们同样写代码验证。
重写EventBtn类,如下,
修改onCreate中的代码
修改main布局文件
修改布局文件,让三个按钮完全重合,加入的顺序,分别是btn1,btn2,btn3,如果按照我们的结论,触摸事件的传递顺序应该是btn3->btn2->btn1->ViewGroup->Activity,我们运行App,点击按钮,查看打印日志,
我们可以看到,日志 打印的顺序呢,跟我们的结论一致,也就验证了我们的结论。但是我们仔细想来,好像还有点问题,就是我们一个按钮点击的动作,至少应该包含了ACTION_DOWN,ACTION_UP,二个触摸事件,但是我们打印的日志,显然只是一个触摸事件,那这是为什么呢?
为了搞清楚上面的问题,我们继续来到View的dispatchTouchEvent方法中,我们看到下面的一段代码
我们也很好读懂,如果事件是ACTION_UP或者ACTION_CANCEL,表示触摸结束,但是还有第三种是 如果触摸事件是ACTION_DOWN 并且result==false,同样停止嵌套的触摸事件传递,即后面的ACTION_MOVE,ACTION_UP都不会触发,因为在我们上面的例子中,没有消费触摸事件,返回的都是false,所以只触发了ACTION_DOWN事件。同样,我们可以利用代码来验证我们上面的结论,我们打开EventBtn类中dispatchTouchEvent方法中的日志打印代码,同时我们将btn3的onTouchListener方法返回true,消费掉触摸事件,然后运行App,点击按钮,查看日志。
我们看到,这里一次按钮点击触发了4个事件,分别是ACTION_DOWN,ACTION_MOVE,ACTION_MOVE,ACTION_UP,同时所有事件全部被btn3消费掉了,这就验证了我们上面的结论,因为此时我们让btn3消费了ACTION_DOWN事件,所以后续触摸事件得以继续触发。通过上面的知识,如果我们不希望响应触摸事件,可以给ACTION_DOWN触摸事件,返回false,或者如果我们希望提前结束一个触摸事件周期,可以给targetView 发送 ACTION_CANCEL事件,如下:
关于Android中ViewGroup的触摸事件传递就介绍到这里,难免很多地方有错误纰漏,但是我能够坚持把这些写出来,我觉得我已经有一点小小的提高了,很开心~~