开发常见的问题是,当希望控件响应某个事件时,其父View会将其拦截掉的尴尬局面,例如ViewPager会把每个page的横向滑动事件拦截掉,如果希望page里面的view可以横向滑动,而viewpage不跟着滑动就好了
面对这个问题,先来了解一些安卓的事件拦截机制,掌握其大概的流程,就可以针对具体的业务玩转事件拦截了。
先打个比方
公司里面有三层,第一层是总经理,第二层是部门经理,第三层是最底层的你。
某一天董事会让总经理分配一下任务,按照常规流程,总经理接到任务后,先把任务拆分,分配到各个部门经理,部门经理们接受任务后,再拆分,分配到最底层的劳动者,也就是你啦。你干完任务之后呢,向部门经理汇报,部门经理觉得满意,就签上自己的名字,再把任务完成的结果汇报给总经理,总经理最后审查,宣布任务完成。整个任务下来,先是由顶层往下传递,底层完成后,再反馈给顶层。代码也是来源于生活,这种模式也被应用于安卓的事件传递,处理机制中。
没错,总经理就是最顶层的布局,部门经理就是中间层的布局,你就是最底层的控件。当顶层捕获到一个事件时,会把这个事件一级一级的传递下去,直到底层控件接收到并处理掉,处理完之后再向上级反馈。
下面通过demo代码来演示这个机制
先创建3个自定义的控件ViewGroupA,ViewGroupB,MyView,继承于RelativeLayout
重写三个方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("xyz","MyView dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("xyz","MyView onInterceptTouchEvent"+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("xyz","MyView onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
其他两个类也类似,就不写出来了
布局效果如下图
点击MyView
打印日志如下:
正常顺序:总经理(ViewGroupA)->部门经理(ViewGroupB)->你(MyView)
先调用dispatchTouchEvent,再调用onInterceptTouchEvent,最后调用onTouchEvent,
事件拦截(dispatchTouchEvent,onInterceptTouchEvent)
返回true,拦截,不继续,返回false,不拦截,继续传递
事件处理(onTouchEvent)
返回true,处理了,不用审核,返回false,没处理,返回上级处理
事件拦截主要关注的是onInterceptTouchEvent,虽然dispatchTouchEvent先被调用,但是一般情况下不用管它,如果我们让ViewGroupA的onInterceptTouchEvent返回true,表示拦截这个事件,继续点击MyView,日志如下
点击myView,但是由于ViewGroupA已经拦截了,所以事件不会传递下去,直接有ViewGroupA全部处理,就好比如总经理觉得这个任务比较简单,一个人可以搞定了,就不会把任务分配下去,这次我们让部门经理来做一次好人,在最开始的修改上让GroupViewB的拦截方法,返回true,日志如下
作为底层的你就不会接受到任务,因为在任务分配到部门经理的时候,就被拦截了,部门经理完成任务,向上级汇报就好了。
如果父布局把整个事件都拦截下来了,子控件将收不到任何事件,这种情况在项目中是比较少见的,一般父布局只会拦截掉部分类型的事件,一般不会拦截ACTION_DOWN,这是一切事件的开端,拦截的话,子控件就不能接受到任何事件,更何况打断父布局拦截事件呢