android触摸事件
‘壹’ Android Touch事件分发处理机制详解
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(调用将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事件的传递流程,如果发现文中有何错误,希望不吝赐教!
‘贰’ Android 悬浮窗如何能让它和他的的下层一起响应触摸事件
/**
* 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
*
* @param context 必须为应用程序的Context.
*/
public static void createSmallWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (smallWindow == null) {
smallWindow = new FloatWindowSmallView(context);
if (smallWindowParams == null) {
smallWindowParams = new LayoutParams();
smallWindowParams.type = LayoutParams.TYPE_PHONE;
smallWindowParams.format = PixelFormat.RGBA_8888;
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
smallWindowParams.width = FloatWindowSmallView.viewWidth;
smallWindowParams.height = FloatWindowSmallView.viewHeight;
smallWindowParams.x = screenWidth;
smallWindowParams.y = screenHeight / 2;
}
smallWindow.setParams(smallWindowParams);
windowManager.addView(smallWindow, smallWindowParams);
LogPrinter.i(TAG, "添加了小浮标View");
}
}
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
‘叁’ Android-View的事件分发及拦截-父控件和子控件都处理触摸事件的方式
比如接着上篇 Android-View的事件分发及拦截机制简单流程先体验再研究(场景?疑问? 具体?待续...) ,小白现在要实现就是子View和父ViewGroup都响应点击事件。
1. 单纯的都只是响应down事件
这个就很简单了 - 直接子View的**public boolean **onTouchEvent(MotionEvent event) 里面直接返回false就行了。也就是子控件响应了一次down后,接下来就交给父ViewGroup了.(子View就啥几把也干不了了);
2. 响应down和up事件,move啥的
我们知道子View如果onTouch里面返回了true,那么将会处理后续的move,up事件。而不再交给上层父ViewGroup。那父ViewGroup就没办法在Touch里面处理,所以我们只能放到dispatchTouchEvent或者onInterceptTouchEvent中处理这个down,up等事件:
比如dispatchTouchEvent中:
这样的情况就是父ViewGroup先执行点击事件,然后子View再执行。 如果您需要父ViewGroup晚点,可以延时执行啥的。
如果此时,子View的dispatchTouchEvent返回true - 表示拦截,不继续了
那么子View的所有的事件都不会响应了。其实也就是我们的一个事件先传递,touch再处理的树形图:
网络上拔个图来
简单记录下下而已,继续加深理解...这是上一篇的续,还是上一篇....啧啧....后面是官方文档分析来着...
‘肆’ Android可监听的事件类型(提示:用户事件和系统事件,用户事件又分为按键事件和触屏事件)
在android系统中,存在多种界面事件,如点击事件,触摸事件,焦点事件,和菜单事件
用户事件和系统事件等,事件发生时,android界面框架调用界面控件的事件处理函数对事件进行处理。
如:用户事件:
按键事件:keyevent将传递给onkey()函数进行处理
触屏事件:touchevent将传递给ontouch()函数进行处理。
‘伍’ Android 双击单击事件监听手势检测GestureDetector原理及实现
app我们常用的手势有很多的地方,比如右滑关闭界面等。手势控制分为触发动作(Touch Mechanics,用户手指在屏幕上如何动作)和触发行为(Touch Activities,界面上特定动作在特定情境下引发的结果)。这是因为同样的触发动作(如单次触击)在不同情境下可能会带来不同的结果(如轻触,取消,开启/关闭指示),同样单次触发行为(如放大)可能是由多种触发动作(如捏放,双次触击,双次触击拖拽等)实现。
一般情况下,我们知道View类有个View.OnTouchListener内部接口,通过重写他的onTouch(View v, MotionEvent event)方法,我们可以处理一些touch事件,但是这个方法太过简单,如果需要处理一些复杂的手势,用这个接口就会很麻烦(因为我们要自己根据用户触摸的轨迹去判断是什么手势)。
Android sdk给我们提供了GestureDetector类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。虽然他能识别手势,但是不同的手势要怎么处理,应该是提供给程序员实现的。
一.GestureDetector简介
1.组成
GestureDetector类用来识别触摸屏的各种手势,它包含了两个接口和一个内部类:
接口:
OnGestureListener:用来监听手势事件(6种)。
OnDoubleTapListener:用来监听双击事件。
内部类:
SimpleOnGestureListener:用来监听所有的手势。实际上它实现了上述两个接口,不过方法体是空的,需要我们自己写。我们可以继承这个类,重写里面的方法进行手势处理。
2.构造
GestureDetector gestureDetector = new GestureDetector(GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector = new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector = new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
3.方法
(1)onTouchEvent(MotionEvent ev) 分析捕捉到的触摸事件触发相应的回调函数
(2)setIsLongpressEnabled(boolean isLongpressEnabled) 设置“长按”是否可用
(3)setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) 设置双击监听器
4.使用
流程:
首先,系统捕捉屏幕的触摸事件(onTouchListener),这时还未涉及具体手势,只是简单地捕捉到触摸。
接着,在onTouch()方法中调用GestureDetector的onTouchEvent()方法,将捕捉到的MotionEvent交给GestureDetector来处理
最后,还需要实现抽象方法。
可根据需要选择:
重写OnGestureListener并通过构造函数传入gestureDetector
重写OnDoubleTapListener并通过GestureDetector.setOnDoubleTapListener方法传入gestureDetector
重写SimpleOnGestureListener并通过构造函数传入gestureDetector
实现:
注:不要注重我写的类是什么类,要注重实现方法自定义view和activity中都可以,根据需要继承上面三种listener,传入构造函数即可;
public class TestDemo{
Context context;
public TestDemo(Context context){
this.context = context;
}
private GestureDetectordetector;
private void initView(){
detector =new GestureDetector(context, new MySimple());
detector =new GestureDetector(context, new MyGesture());
detector =new GestureDetector(context, new MyDoubleTap());
setOnTouchListener((v, event) -> {
// 事件监听交给手势类来处理
detector.onTouchEvent(event);
return true;
});
}
//与上面二选一
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
// 手势监听器类SimpleOnGestureListener
private class MySimple extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {//一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发;如果除了Down以外还有其它操作,那就不再是Single操作了,所以也就不会触发这个事件
return super.onSingleTapUp(e);
}
@Override
public void onLongPress(MotionEvent e) {//长按事件;
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//在屏幕上拖动事件,只要手指移动就会执行,无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法在ACTION_MOVE动作发生时就会触发他不会执行MotionEvent.ACTION_UP,通常用来实现放大缩小和移动。
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//滑动屏幕,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发;是个甩的动作,这个甩的动作是在一个MotionEvent.ACTION_UP(手指抬起)发生时执行,通常用来实现翻页效果
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public void onShowPress(MotionEvent e) {//down事件发生而move或则up还没发生前触发该事件;
super.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {//down事件用户按下屏幕
return super.onDown(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {//双击事件
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {//双击间隔中还发生其他的动作。通知DoubleTap手势中的事件,包含down、up和move事件(这里指的是在双击之间发生的事件,例如在同一个地方双击会产生DoubleTap手势,而在DoubleTap手势里面还会发生down和up事件,这两个事件由该函数通知)
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {//单击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。
return super.onSingleTapConfirmed(e);
}
}
// 手势监听器类GestureListener
private class MyGesture extends GestureDetector.OnGestureListener{
@Override
public boolean onDown(MotionEvent e) {//down事件用户按下屏幕
return false;
}
@Override
public void onShowPress(MotionEvent e) {//down事件发生瞬间而move或则up还没发生前触发该事件;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {//一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发;如果除了Down以外还有其它操作,那就不再是Single操作了,所以也就不会触发这个事件;
return super.onSingleTapUp(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//在屏幕上拖动事件,只要手指移动就会执行,无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法在ACTION_MOVE动作发生时就会触发他不会执行MotionEvent.ACTION_UP,通常用来实现放大缩小和移动。
return false;
}
@Override
public void onLongPress(MotionEvent e) {//长按事件,超过一定时长触发该事件回调;
super.onLongPress(e);
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//滑动屏幕,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发;是个甩的动作,这个甩的动作是在一个MotionEvent.ACTION_UP(手指抬起)发生时执行,通常用来实现翻页效果
return super.onFling(e1, e2, velocityX, velocityY);
}
}
// 手势监听器类SimpleOnGestureListener
private class MyDoubleTap extends GestureDetector.OnDoubleTapListener {
@Override
public boolean onDoubleTap(MotionEvent e) {//双击事件
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {//双击间隔中还发生其他的动作。通知DoubleTap手势中的事件,包含down、up和move事件(这里指的是在双击之间发生的事件,例如在同一个地方双击会产生DoubleTap手势,而在DoubleTap手势里面还会发生down和up事件,这两个事件由该函数通知)
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {//单击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。
return super.onSingleTapConfirmed(e);
}
}
}
关于onFling()和onScroll()的区别:
onFling()是甩,这个甩的动作是在一个MotionEvent.ACTION_UP(手指抬起)发生时执行,而onScroll(),只要手指移动就会执行。他不会执行MotionEvent.ACTION_UP。onFling通常用来实现翻页效果,而onScroll通常用来实现放大缩小和移动。
关于onSingleTapConfirmed和onSingleTapUp的一点区别: OnGestureListener有这样的一个方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的区别是:onSingleTapUp,只要手抬起就会执行,而对于onSingleTapConfirmed来说,如果双击的话,则onSingleTapConfirmed不会执行
SimpleOnGestureListener是GestureDetector类的一个内部类,该类是static class,也就是说它实际上是一个外部类。可以在外部继承这个类,重写里面的手势处理方法。
1.OnDoubleTapListener是用来检测鼠标双击事件的
2.SimpleOnGestureListener实际上实现了OnGestureListener 和OnDoubleTapListener,所以它可以完成以上提到的所有手势识别(9种)