Android View原理浅析——View的事件体系
View是什么
首先我们需要理解什么是View。View是安卓中所有控件的基类,无论是简单的TextView、Button,还是复杂的LinearLayout、ListView,它们的共同基类都是View,ViewGroup其实也是继承了View。Button是一个View,而LinearLayout是View的同时也是ViewGroup,ViewGroup的内部可以有子View,而子View同样可以是ViewGroup,以此类推。
View的位置参数
View的位置由它的四个顶点来决定,分别对应了View的四个属性——top、left、right、bottom。需要注意的是,这些坐标都是相对于View的父容器而说的,是一种相对坐标。View的坐标和父容器的关系如图。在Android中,x、y轴的正方向分包为右、下。
很容易得出,View宽高和坐标关系如下:
width = right - left
height = bottom - top
通过View的getLeft()、getRight()…方法可以得到这四个参数。
从Android 3.0开始,View增加了几个额外的参数:x.、y、translateX、translateY。x、y是View左上角的坐标,而translateX,translateY则是View左上角相对于父容器的偏移量。这几个参数也是相对父容器的坐标,并且translateX、translateY的默认值为0。与前面的参数一样,View也为它们提供了get/set方法。
需要注意:View平移过程中,top、left表示的是原始左上角位置,并不会改变。此时发生改变的是x、y、translatX、translateY这四个参数。
MotionEvent和TouchSlop
MotionEvent
手指接触屏幕后产生的一系列事件中,典型的事件类型有一下几种:
- ACTION_DOWN:手指刚刚接触屏幕
- ACTION_MOVE:手指在屏幕上移动
- ACTION_UP:手指从屏幕松开的一瞬间
正常情况,一次手指触碰屏幕的行为会发生一系列点击事件,比如如下情况:
- 点击屏幕后松开,事件顺序:DOWN->UP
- 点击屏幕滑动一会松开,事件顺序:DOWN->MOVE->…->MOVE->UP
上述的情况是典型的事件序列,同时通过MotionEvent对象我们可以得到发生点击事件的x,y坐标。为此,系统提供了两组方法 getX/getY 和 getRawX/getRawY。它们的区别:getX/getY返回的是相对于当前View左上角的x,y坐标。getRawX/getRawY返回的是相对于屏幕左上角的x,y。
TouchSlop
TouchSlop是系统能识别的被认为滑动的最小距离。如果滑动时距离小于这个常量,则系统不认为这是滑动。它的值在不同设备上可能不同,通过 ViewConfiguration.get(getContext()).getScaledTouchSlop() 即可获取这个常量。这个常量的可以帮助我们在处理滑动时做一些过滤。
在源码中可以找到这个常量的定义,它处于frameworks/base/core/res/res/values/config.xml文件中。
VelocityTracker、GestureDetector 和 Scroller
VelocityTracker
顾名思义,速度追踪,用于追踪手指在滑动时的速度,包括水平和竖直方向的速度。使用过程很简单,首先在View的onTouchEvent中追踪当前单击事件的速度:
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
接着,当我们想知道当前滑动速度,可以采用如下方式:
VelocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
有两点需要注意:
一、获取速度前必须先计算速度,即先调用computeCurrentVelocity()方法。
二、此处的速度是指一段时间内手指划过的像素数,当手指从右往左划时,速度为负值。
最后,不需要使用它时,调用clear及recycle方法即可重置并回收内存。
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector
手势检测,用于辅助用户单击、滑动、长按、双击等行为。
使用GestureDetector,首先需要创建一个GestureDetector对象并实现OnGestureListener接口,根据需要还可以实现OnDoubleTapListenr从而监听双击事件。
GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕无法拖动的情况
mGestureDetector.setIsLongpressEnabled(false);
接着接管View的onTouchEvent方法,在其中添加如下实现:
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;
之后,我们可以有选择地实现 OnGestureListener和OnDoubleTapListener中的方法了,这两个接口的方法介绍如表:
方法名 | 描述 | 所属接口 |
---|---|---|
onDown | 手指触摸屏幕一瞬间,由一个ACTION_DOWN触发 | OnGestureListener |
onShowPress | 手指轻轻触摸屏幕,未松开或拖动,由一个ACTION_DOWN触发(注意与onDown的区别,它强调的是没有松开或拖动的状态) | OnGestureListener |
onSingleTapUp | 手指触摸后松开,伴随一个MotionEvent ACTION_UP而触发,是单击行为 | OnGestureListener |
onScroll | 手指按下屏幕并拖动,由一个ACTION_DOWN,多个ACTION_MOVE触发,是拖动行为 | OnGestureListener |
onLongPress | 用户长按 | OnGestureListener |
onFling | 用户按下触摸屏,快速滑动后松开,由一个ACTION_DOWN,多个ACTION_MOVE和一个ACTION_UP触发,是快速滑动行为 | OnGestureListener |
onDoubleTap | 双击,由两次连续的单击组成,不可能与onSingleTapConfirmed共存 | OnDoubleTapListener |
onSingleTapConfirmed | 严格的单击行为(注意它与onSingleTapUp的区别,如果触发了onSingTapConfirmed,那么后面不可能再跟着一个单击行为,即只可能是双击,不可能是双击中的一次单击) |