在Android中,界面元素都可以接受事件,像我们最经常用到的对Button 组件添加点击事件,往往对其进行setOnClickListenner, 重写其中的 onClick() 函数,这样我们界面上的button 就能接受我们的点击事件,当我们需要对组件更加复杂的触摸事件时,我们往往会重写 onTouch() 以及onTouchEvent 函数,那么这几个函数之间的关系到底是怎样的呢,同时在同一个界面上的相同位置上组件对触摸点击事件的响应原理是怎样的呢?它们相互之间会有怎样的影响呢?在项目中接触到类似的问题之后,参考了郭林老师有关于Android view 上事件分发机制的介绍(
http://blog.csdn.net/guolin_blog/article/details/9153747
http://blog.csdn.net/guolin_blog/article/details/9097463
),自己总结归纳如下。
1 dispatchTouchEvent 方法
此方法存在View中,对任何View的触摸类事件进行响应,所以一切组件的触摸点击事件都需要从此函数入手,
此函数的源码在笔者看到的android22 时,已经被更新,但是大体关系仍跟郭林所讲一致。
在此方法中我们可以看到 onTouch() 和 onTouchEvent() 这两个 函数,同时我们注意到 这两个函数的返回值对函数的执行有着重要的影响,如果onTouch() 返回true ,则此函数直接退出,不会执行 onTouchEvent() 函数,因此我们在重载onTouch() 的时候,会发现其默认返回的是false,这样就会继续执行onTouchEvent() , 这个是在你要重载onTouch() 方法时要注意的,为什么onTouch()函数返回false
2 onTouchEvent 方法
onTouchEvent 方法不同于 onTouch() 和 onClick,它们都是监听接口中的函数,也就是需要你set相应的listener的时候才会有可能调用,onTouchEvent方法就是view 里面的函数,所以如果你重写此函数,在上面的情况下,就会调用onTouchEvent 方法,并且,我们在阅读它的源码中可以看到,通过对传进来的event 参数,我们可以获得当前点击事件的坐标值和事件类型 MotionEvent.ACTION_DOWN MotionEvent.ACTION_UP MotionEvent.ACTION_MOVE 等,通过这些判断在不同的动作类型下执行不同的函数方法,我们会在其中发现一个 peformClick 的函数,于是猜测是onClick 方法的执行之处,跟踪过去可以发现,果然是这样的。onClick方法是在onTouchEvent 中实现的,因此如果你在onTouch()方法中返回了true ,onClick 方法将不会得到执行。
往往触摸的时候都会紧跟着一系列的动作事件,如果你重写onTouchEvent 事件,并对其不同的MotionEvent的类型进行打log,你往往会看到一系列的log 打印出来,这里就会有另一个问题,这么多事件能够被检测到,说明在不停的调用onTouchEvent()方法,能够让这些事件连续检测到保证是什么呢,我们看到onTouchEvent() 方法中 case break后的返回值,是true ,也就是说每次调用都需要返回true 才会被检测到下面的事件,同时要进入onTouchEvent 中可以得到事件类型检查的条件还有当前控件必须是可以点击的,clickable == true ,否则也不会执行,imageview 默认是不可点击,因此如果想要对其进行点击事件等,可以在这里注意一下。最后我们可以看到 最后onTouchEvent() 函数里面最后返回了false ,为什么是false ,待下面分析。
首先是学习了在viewgroup 中添加view的时候在viewgroup 中的 view[] childeren 数组中的顺序是从 上到下依次填入的,而在 viewgroup 中的dispatchTouchEvent() 中,我们可以看到 它是倒序对 view[] chideren 的数组进行遍历,因此猜测事件都会在最前面的ui控件上进行反应。
onIntercepTouchEvnet() 方法作为进入dispatchTouchEvent() 中遍历子view 的关键条件,默认返回false ,如果重写返回true ,则子View不会得到触摸事件。源码已经重新改写,不能直接看到遍历子view 的时候调用子view 的dispatchTouchEvent() ,被封装在了另一个函数中dispachTransformTouchEvent() , 这个函数的返回值,依然是返回的child.dispatchTouchEvent() 的返回值,所以郭林老师那里分析的思路依然可行。
那么 子view 之间又是怎样连接起来的呢, 找直接退出循环的break 语句,会发现如果 上面作为条件的 dispatchTransformTouchEvent() 的函数的返回值是true ,那么就不会再进行循环,说明当前的事件就被此Child View给消耗掉了,不会再往下传递了,因此如果想要中间层的chilld 得到touch事件,前面view 的dispatchTouchEvent () 方法返回false ,而让这个函数返回false ,就又要使 onTouchEvent() 函数的返回值为false ,如果要执行onTouchEvnet() 函数,那么就必须让onTouch() 函数 返回false()《当然有点极端,也可以是其他条件返回false》 这也是为什么在上面分析的时候onTouchEvent最后返回了false的原因。
因此最终要的一点就是,如果中间层的view要响应touch事件,而它上面覆盖的view如果重写了onTouchEvent 方法,则一定要注意它的返回值,根据不同的情况进行相应的处理。
下面的方法是判断当前页面是否点击的判断
public boolean onTouchEvent(MotionEvent event){
if(flag){
switch(event.getAction()){
case MotionEvent.Action_DOWN:
mLastClickTime = System.currentTimeMillis();
mDownInScreenX = event.getX();
mDownInScreenY = event.getY();
break;
case MotionEvent.Action_UP:
if(System.CurrentTimeMillis() - mLastClickTime < CLICK_SPACING_TIME){
mUpInScreenX = event.getX();
mUpInScreenY = event.getY();
if(Math.abs(mDownInScreenX - mUpInScreenX) < 10 &&
Math.abs(mDownInScreenY - mUpInScreenY) < 10){
if(mOnClickListener!=null)
mOnClickListener.onClick();
}
}
break;
return true; // 在当前view 事件不会再分发下去
}
return false; // 分发到下面view层,该层不做特殊处理
}