注:配图来自网络。。。

 本文通过图文配合的方式讲解Android触摸反馈。(具体的可参考任主席的《开发艺术探索》或者自己查看系统源码(工具:Source Insight 4)


触摸反馈:

点击、长按、滑动等他们的本质原理。把一系列的触摸事件解读为对应的操作,然后根据解读出来的操作给出相应的反馈,这就是触摸反馈的本质。其中,触摸事件不是独立的,是成序列的,成组的。每一组事件以按下事件为开头,以抬起事件或取消事件为结束。其中ACTION_CANCEL是特殊的,对应的是事件序列的非人为的提前结束。每一个触摸事件都会交给View.onTouchEvent(MotionEvent event)去处理。参数event是代表事件类型(按下、抬起或其他)、坐标、其他各种信息。

大致写法如下:


事件分发机制:(从上到下)

为了解决触摸冲突而设置的机制。

例子:

矩形是父view,有2个子view:圆形按钮(可点击)和"Lorem Ipsum"文字(不可点击)。这时点击按钮是可以子view触发事件,但是点击文字是触发的父view的点击事件。

Android是如何做到的?(触摸事件分发)

如果一个view对这个down的onTouchEvent()没有响应,那么它就会继续向下,直到遇到第一个做出响应的view,这个向下的过程才会结束。这个时候这个view就成了这组事件的接收者。这个事件的后续事件都会直接发送给这个view,不会给它上面的view和下面的view.直到这组事件的结束,即ACTION_UP/ACTION_CANCEL事件。

这是一个很易懂的逻辑,离用户最近的可触摸的控件是这组事件的响应者。

其中“响应”在代码中的体现如下图:

onTouchEvent返回return true;表示消费了该事件。更直观的理解为,告诉android,我希望处理以这个down事件为起始点的事件流,你把这之后的后续事件都交给我。其实只要down事件的返回值写为true,其他up,move等事件的返回值是没有影响的,你全部写为true也行。


事件的拦截机制:(从下往上)

案例:

可点击的控件是在列表里面。点击某个控件会触发点击事件。而把手机放到屏幕上滑一下,列表也是会滑动的。为了符合直觉,安卓的触摸是从上往下传递的被某个控件消费后就不会再往下传了。那么隔着一个按钮实现的滑动怎么做到的?

答案:触摸事件的拦截机制。

其实在触摸事件的分发(从屏幕的顶部向下分发)之前还有个过程:触摸屏幕的时候,每个触摸事件到达onTouchEvent()之前,android会从整个activity的最底部的那个view(根view)去向上一级一级的询问:你要不要拦截这组事件。
拦截的意思就是说事件我就不交给子view了,我就自己来处理了。

具体在实现上,它是通过调用viewgroup的onInterceptTouchEvent()实现。也就是当一个事件发生的时候,首先会从底部的view向上递归的调用每一级的子view的onInterceptTouchEvent()去询问该子view是否要拦截这组事件,默认是返回false不拦截。如果他要返回false,那么就会继续向上去问它的子view询问是否拦截。如果整个流程都走完,全部都返回false,那么就会走第2个流程:onTouchEvent()从上往下,如果中途有某个view想要拦截,那么就可以在onInterceptTouchEvent()里面返回true,那么事件就不会再交给它的子view,而是交给自己的onTouchEvent()去处理,并且这之后的所有后续事件都会被自动拦截了,不会交给它的子view,也不会交给它的onInterceptTouchEvent(),而是直接交给它的onTouchEvent()。另外onInterceptTouchEvent()和onTouchEvent()有一点的不同在于是否消费这组事件onTouchEvent()是在ACTION_DOWN里确定的,如果在ACTION_DOWN事件里的onTouchEvent()里面返回false,以后你就和这组事件无缘了,没有第二次机会。而onInterceptTouchEvent()则是你在整个过程中,都可以对事件流中的每个事件进行监听,你可以先行观望,给子view一个处理事件的机会,而一旦事件流的发展达到了你的触发条件,比如用户现在在滑动,你可以立刻返回true,立刻实现事件流的接管,这样就2不耽误,既让子view有机会去处理事件,又可以在需要的时候把处理事件的工作给接管过来。

另外,对于onInterceptTouchEvent()返回true的时候,除了完成事件接管,这个view还会对它的子view发送一个ACTION_CANCEL取消事件。因为你在接管事件的时候,上面的子view可能正处于一个中间状态。比如先点击一个按钮,然后再一滑动,你就知道用户其实是在滑动,这个时候你就把事件拦截了,但现在上面的按钮是按下状态,我们需要恢复它,因为按钮人家正等着后续事件,我们不能就这么让按钮没搞头了啊。所以onInterceptTouchEvent()返回true的时候,子view会接收到一个cancel事件,通知该子view,这个事件你不要再管了,把你自己的状态恢复过来。

 

代码如下图:

子view不希望被父view拦截事件的情况:

例子:

比如一个列表需要支持长按重排功能。长按列按钮的按钮是移动按钮而不是滑动列表项,这个时候就需要父view不要拦截事件。

解决办法:

这个时候就需要requestDissallowInterceptTouch()方法。这个 requestDissallowInterceptTouch()方法不是用来重写的,是用来调用的。在事件过程中,在子view里去调用父view的这个 parent.requestDissallowInterceptTouch()方法,父view就不会再尝试通过onInterceptTouchEvent()方法来进行拦截了,并且它是一个递归方法,它会阻止每一级父view的拦截,且仅限在当前的事件流,也就是说在用户操作之后一切恢复正常。
 

拓展:

其实,onInterceptTouchEvent()和onTouchEvent()都是在dispatchTouchEvent()里面发生的。
一个
事件分发的过程实质上就是从根view递归的调用了dispatchTouchEvent()的过程。

 


自定义触摸反馈的关键:

1. 重写onTouchEvent(),在里面写上你的触摸反馈算法,并返回true(关键是ACTION_DOWN事件时返回true)。
2. 如果是会发生触摸冲突的ViewGroup,还需要重写onInterceptTouchEvent(),在事件流开始时返回false,并在确认接管事件流时返回一次true,以实现对事件的拦截。
3. 当子 View 临时需要阻止父 View 拦截事件流时,可以调用父 View 的requestDisallowInterceptTouchEvent(),通知父 View 在当前事件流中不再尝试通过onInterceptTouchEvent()来拦截。

注:如果还想知道底层源码如何调用实现的,可参考《开发艺术探索》或者自己查看源码。

 

 

更多相关文章

  1. 【Android归纳决】回调机制在Android中的应用与实战
  2. 基于Android(安卓)RIL层实现来电拦截的技术原理(二)
  3. 一个android显示远程txt的代码例子
  4. Android使用GestureDetector实现手势滑动效果
  5. Android中button实现onclicklistener事件的两种方法
  6. 点击事件内部类中使用内部类
  7. Android(安卓)listView 中响应Button点击事件
  8. Android自定义TextView中的超链接点击事件处理
  9. Expected receiver of type com.xx.xxxActivity, but got androi

随机推荐

  1. PHP如何获得地址栏的特殊字符呢?
  2. 使用PHP显示所有列条目
  3. ThinkPHP5.0框架开发--第10章 TP5.0验证
  4. 在Amazon EC2 linux AMI上安装FFMPEG-Php
  5. php的控制器链
  6. 仅在两个字符串之间移除空白。
  7. 如何使用php和mysql处理加密的私人消息
  8. 在PHP中更改关联数组索引的位置
  9. 使用php将文件上传到AWS S3
  10. Ajax上传插件。handleError未找到