重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
touch是触摸,
成都服务器托管,创新互联建站提供包括服务器租用、光华机房服务器托管、带宽租用、云主机、机柜租用、主机租用托管、CDN网站加速、空间域名等业务的一体化完整服务。电话咨询:18982081108
分为ACTION_DOWN(刚接触屏幕那一下)
ACTION_MOVE(在屏幕上移动)
ACTION_UP(抬起来)
以上3个都可以有不止一个触摸点来触发
click是一个手指DOWN,过一段时间再UP,并且此时间间隔不足以触发长按,
同时MOVE小于一定范围,的一个组合。
用注解, 打个@SuppressLint("ClickableViewAccessibility")
这个警告是说,有可能会和点击事件发生冲突
如果你在touch中返回了true,那么就不会响应onClick事件了
你必须调用一下view.performClick(),才会触发
view.setOnTouchListener(new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return false;
}
});
Touch事件的传递
首先我们要了解在android系统里面有几个地方会走touch事件,这个是老生常谈的问题了,但是我还是希望写一下这个问题,因为温故而知新嘛,我们首先得知道VIew类这种不能作为容器的类只会有这两个函数:
[java] view plain copy print?
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
三个手指向屏幕传递的东西可以被抽象为一个个的触摸点(Pointer)。
按照触碰到屏幕的顺序来分,每个Pointer都有一个index,这个算法有点特别。现在举例来帮助你理解。
看到Index,你是否想起了数组下标?没错你可以认为这三个Pointer被安置在了一个数组里,数组大小为Pointer的个数。
有一天,我拿右手的三根手指(食指A,中指B,无名指C)在屏幕上做滑动手势,我是 先用食指A 接触屏幕, 再拿中指B 接触到, 最后拿无名指C 接触到屏幕,这三根手指的起始位置都贴着屏幕的 最左边 也就是 X轴接近于0 的地方。这时候 以触碰到屏幕的次序为依据 ,这三根手指分别被赋予了各自的index,其中 食指A最先 接触,index为 0 ; 中指B第二个 接触,index为 1 ; 无名指C第三个 接触,index为 2 ;
无聊透顶的我在往右滑动的过程中,把 中指B松开 了,这下 index为1的中指B 从数列中被移除了,安卓说:哦, index为1 的这个地方被移除过Pointer,并且这个index为1的位置还是 第一个被移除 的,我记录下来了。
移除完后呢,总不能空着吧,现在就 两个 Pointer在屏幕上,我总不能按三个来算吧。我是把目前 index为0 的 Pointer(食指A) 转移到index为1的位置上,还是把目前 index为2 的 Pointer( 无名指C ) 转移到index为1的位置上呢?
是的,谷歌和你一样都很机智,他们把 index为2的向前移 了,这样数组尺寸就变小了。
既然之前说了我无聊透顶,目前我好像还不够无聊,为了贯彻这个词语,我在快要滑到 屏幕右侧 的时候,我 又加了 一根手指进来,这次加的还不是原来那根 黄金中指B ,而是我的 小拇指D !而且非常风骚地伸到了屏幕的 左侧 去了,这意味着我这个 Pointer小拇指D的X值是很小 的,正当我盯着 index为2的位置 观看我风骚小拇指创造的位置数据时,惊奇地发现我的小拇指被赋予的 index竟然为1!!! 在后来的实验中, 不管我放的 是原来率先释放的中指B还是什么一阳指,卫生指。他们被赋予的 index都是1!!!
比如index分别为0,1,2,3的四个Pointer,其中 index为2的Pointer率先 中途移除,然后 index为3的Pointer接着被移除 ,系统将 记录 哪个index是 最先出现空缺 的,哪个index是 第二个出现空缺 的,然后接下来新产生的Pointer将按 产生的顺序 ,先来的放到最先出现空缺的index上,在这里,最新来的Pointer将被放置在 最先出现空缺的 index为 2 的位置上。 下一个来的 Pointer将被放置在 index为3 的位置上.
Android应用的开发过程不可能不涉及到Touch事件的处理,简单地如设置OnClickListener、OnLongClickListener等监听器处理View的点击事件,复杂地如在自定义View中通过重写onTouchEvent来捕获用户交互事件以定制出各种效果,在使用的过程中或多或少会遇到一些奇怪的Bug,让你对Touch事件“从哪来,到哪去”产生迷之疑惑,经过多少次徘徊之后终于决定系统的分析下源码,本文就给大家分享下我的收获。
MotionEvent作为Touch事件的载体,采用时间片来管理Touch事件所有相关行为的数据,本文这样理解时间片这个概念:
通常MotionEvent会将触发当前事件的Pointer作为主要Pointer,其PointerIndex为0,而MotionEvent通过提供getX()这类不带index参数的接口以更方便的操作主要Pointer的数据。
了解了MotionEvent的组成结构之后,接下来就可以分析MotionEvent包含的事件类型了,MotionEvent通过getAction接口来获取事件Action,而Action中低8位地址存储的是事件类型(对于触摸事件来说,主要包括Down、Move、Up、Cancel、PointerDown、PointerUp),高8位地址存储的是PointerId(当事件类型为PointerDown、PointerUp时)。通常来说事件会以Down开始,以Up或Cancel结束,各事件所承担的角色以及各自的特点在分析事件分发与处理的过程时再详细说明。
另外,MotionEvent中的Flag需要说明一下:
本文仅分析Touch事件在Framework中Java层的传递,因此从事件传递到Activity开始分析。当Touch事件传递给Activity时,会调用Activity.dispatchTouchEvent(MotionEvent),Activity会将事件传递给其Window进行处理,实际会调用PhoneWindow.superDispatchTouchEvent(MotionEvent),PhoneWindow会将该事件传递给Android中View层级中的顶层View(即DecorView)进行处理:
在Window未设置Callback的情况下,会调用父类的dispatchTouchEvent,DecorView继承自FrameLayout,然后FrameLayout并未实现dispatchEvent,因此最终调用ViewGroup.dispatchTouchEvent,也就是Touch事件分发的核心逻辑所在,前文中提到MotionEvent中事件类型主要包括Down、Move、Up、Cancel、PointerDown、PointerUp,而dispatchTouchEvent根据事件的不同类型会做不同处理,因此这里分别进行分析:
Down事件处理
非异常情况下,Touch事件的事件周期总是以Down事件开始的,因此Down事件在整个事件分发逻辑中起关键作用,将决定了后续Move、Up及Cancel事件的处理主体,先看一张Down事件分发的流程图:
从流程图中可以看到,Down事件的分发逻辑主要目的在于寻找到能处理该Touch事件的View控件(该View为以当前ViewGroup为Root节点的View层级中的View,利用寻找到的View创建事件处理Target),整个处理逻辑主要包含以下几步:
Move、Up、Cancel事件处理
完成Down事件的分发逻辑后,就确定了该Down事件后续Move、Up及Cancel事件的处理主体(注意:这里并没有确定PointerDown事件的处理主体,关于PointerDown事件的分发逻辑稍后分析),先通过一张流程图来感受下Move、Up、Cancel事件的分发逻辑:
从流程图可以看出,对于Move、Up、Cancel事件的分发步骤如下:
PointerDown事件处理
PointerDown事件是在支持多Pointer(调用setMotionEventSplittingEnabled将FLAG_SPLIT_MOTION_EVENTS置位)的环境下,当有新的Pointer按下时产生的,该事件处理的特殊性在于会重新遍历View层级,寻找可以处理新Pointer事件的Target,具体流程参考Down事件的分发逻辑;遍历结束若仍没有找到处理该事件的Target,则会将新Pointer的处理权设置给已有Target中最早被添加的Target。完成Target的寻找之后,会将该事件通过dispatchTransformedTouchEvent传递至所有已有Target进行处理,可以通过下面流程图,对PointerDown事件的处理有一个更全局的认识:
PointerUp事件处理
相对于Up事件来说,对于PointerUp事件的处理区别在于当传递至所有已有Target结束之后并不能标记以Down事件起始的整个事件周期结束,仅能标记其关联Pointer(以PointerDown事件起始)的事件周期结束,因此不会清除所有状态,而仅会从已有Target中移除掉与该Pointer相关的部分。
onInterceptTouchEvent
在ViewGroup进行事件分发的过程中,会调用该函数来确定是否需要拦截事件,当该函数返回true时该事件将会被拦截,即不会进行正常的View层级传递,而是直接由该ViewGroup来处理,而拦截后的操作需要根据拦截事件的类型不同而不同:
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)
在将事件传递给Target进行处理之前会调用该函数对MotionEvent进行处理:
MotionEvent.split(int idBits)
判断一个View控件是否消费一个事件,是由View.dispatchEvent的返回值来决定的,而View.dispatchEvent用于寻找事件的最终消费者,话不多说,还是通过一张流程图来个直观感受:
从流程图中可以看出,View会根据ouch事件对Scroll状态进行调整,并寻找该事件的最终处理器:
View.dispatchEvent将向其直接ViewGroup返回是否消费掉该事件,返回值将决定上级ViewGroup是否需要继续询问其他子View是否需要消费该事件。这就是View中分发事件的逻辑,真是简单粗暴!
从View.dispatchEvent的分析中可以发现当未对View设置mTouchListener或mTouchListener未消费掉该事件时,Touch事件最终将由View.onTouchEvent来决定是否消费,自定义View可以重写该方法实现自身的逻辑,此处仅分析View中的通用处理逻辑:
从上述分析可以很开心地发现熟悉的onClick及onLongClick事件的产生逻辑,若是之前没看过类似的文章,应该会有原来如此的感觉吧,哈哈~~
至此,Touch事件的分发与处理流程算是走通了,个人看完整个源码之后有种豁然开朗的感觉,能很清晰的分析向“为什么事件有时候传到某个View有时候却不传?”、“有时候只传前面几个事件后面却不传了?”等问题,也希望本文的分析能让你更清晰地感知Android中Touch事件的传递流程,如果发现文中有何错误,希望不吝赐教!