1.什么是View?
View是Android中所有控件的基类,不论是简单的Button还是较为复杂的ListView或者LinearLayout,它们的共同基类都是View。所以说Android中的View是一种界面层的控件的一种抽象。除了View以外还有ViewGroup,从字面意义上来看它是控件组的意思,也就是说它的内部可以包含许多的控件,其实ViewGroup也是继承自View,这就意味这View本身可以是单个控件,也可以是由多个控件组成的控件组。通过这样一系列的关系就形成了View树的结构,如下图所示。
根据上面View树的图示可以知道,ViewGroup内部是可以有子View也可以有ViewGroup,而View内部没有子View。明白了以上的View的这种层级关系有利于让我们理解View的工作机制和View的事件分发机制。
2.View的位置参数
View的位置参数主要由它的四个顶点来决定,分别对应于它的四个属性(top、bottom、left、right),其中top表示当前View的上边界距父容器的距离也就是View的左上角的纵坐标,bottom表示当前View的下边界距父容器的距离也就是View的右下角的纵坐标,left表示当前View的左边界距父容器的距离也就是View的左上角的横坐标,right表示当前View的右边界距父容器的距离也就是View的右下角的横坐标,入下图所示。
由上图可以看到,在Android中X轴和Y轴的正方向分别是向右和向下,并且我们可以根据View的这四个属性很容易的得到它的高和宽
width = right - left
height = bottom - top
可以通过下面的四个方法,分别获取View的这四个属性
top = getTop();
bottom = getBottom();
left = getLeft();
right = getRight();
了解Android动画的同学应该知道,在Android3.0之前是不能用属性动画的,只能用补间动画,而补间动画所做的动画效果只是将View的显示转为图片,然后再针对这个图片做透明度、平移、旋转、缩放等效果。这带来的问题是,View所在的区域并没有发生变化,变化的只是个“幻影”而已。也就是说,在Android 3.0之前,要想将View区域发生变化,就得改变top、left、right、bottom。如果我们想让View的动画是实际的位置发生变化,并且要兼容3.0之前的软件,该怎么办呢?为了解决这个问题,从3.0开始,加了几个新的参数:x、y、translationX、translationY。
x和y分别表示View的左上角坐标,translationX和translationY是View左上角相对于父容器的偏移量,这四个参数都是相对于父容器的偏移量,并且translationX和translationY的默认值为0,其中x、y、translationX、translationY的关系如下。
x = left + translationX
y = right + translationY
需要注意的是,在View平移的过程中,top和left表示的是原始左上角的位置信息,其值并不会改变,此时会改变的是x、y、translationX、translationY这四个参数。
3.MotionEvent和TouchSlop
(1)MotionEvent
MotionEvent即为移动事件,我们都知道,每个触摸事件或者滑动事件都代表用户在屏幕上的一个动作,而每个动作必定有其发生的位置,在MotionEvent中就有手指接触屏幕后所产生的一系列事件。
ACTION_DOWN — 手指刚接触屏幕
ACTION_MOVE — 手指在屏幕上移动
ACTION_UP — 手指离开屏幕
在正常情况下,一次手指与屏幕的交互会生成一系列的事件,主要考虑一下两种情况:
点击屏幕后松开 — 事件包括一个ACTION_DOWN和一个ACTION_UP
点击屏幕滑动一段距离再松开 — 事件包括一个ACTION_DOWN、多个ACTION_MOVE和一个ACTION_UP
同时在通过MotionEvent对象我们可以得到点击事件发生的x、y的值,为此系统为我们提供了两组方法来得到x和y的值,包括getX/getY和getRawX/getRawY,它们之间的区别很简单,getX/getY的返回值是相对于当前View左上角的x和y坐标,而getRawX/getRawY的返回值是相对于手机屏幕左上角的x和y坐标,入下图所示。
(2)TouchSlop
TouchSlop指的是系统能够被识别出的最小滑动距离,也就是说只有手指在屏幕上滑动的距离大于TouchSlop这个常量系统才认为进行了滑动操作,否则系统就不认为你是在进行滑动操作。TouchSlop的值是一个常量,和设备有关,在不同的设备中这个值有可能是不一样的,可以通过如下方式获取这个常量:ViewConfiguration.get(context).getScaledTouchSlop(),同时也可以通过源码获取这个常量的值,如下所示。
/**
* Distance a touch can wander before we think the user is scrolling in dips.
* Note that this value defined here is only used as a fallback by legacy/misbehaving
* applications that do not provide a Context for determining density/configuration-dependent
* values.
*
* To alter this value, see the configuration resource config_viewConfigurationTouchSlop
* in frameworks/base/core/res/res/values/config.xml or the appropriate device resource overlay.
* It may be appropriate to tweak this on a device-specific basis in an overlay based on
* the characteristics of the touch panel and firmware.
*/
private static final int TOUCH_SLOP = 8;
TOUCH_SLOP就是TouchSlop的值。
4.VelocityTracker
我们知道,很多ViewGroup中,假设手指滑动的距离相同,但是滑动速度不同,那么滑动速度越快,ViewGroup中内容滚动的距离越远。那么如何识别用户滑动的速度呢?当然了,你可以在onTouchEvent中不断的监听计算,但是那样的代码太臃肿了,而且容易算错,好在Android系统内置了速度追踪类VelocityTracker,有了它我们就不用担心如何计算速度追踪了。
首先需要在View的onTouchEvent方法中追踪当前单击事件的速度
VelocityTracker vt = VelocityTracker.obtain();
vt.addMovement(event);
接着当我们想知道当前的滑动速度时,这个时候可以采用如下方式来获得当前的速度
vt.computeCurrentVelocity(1000);
int xv = (int) vt.getXVelocity();
int yv = (int) vt.getYVelocity();
这里设置的时间间隔为1000ms,很显然,速度的计算为(终端位置-起始位置) / 间隔时间,最后,当不需要使用它的时候,需要调用clear方法来重置并且回收内存
vt.clear();
vt.recycle();
5.GestureDetector
手势检测用于辅助检测用户的单击、滑动、长按、双击等行为,首先需要创建一个GestureDetector的对象并且实现OnGestureListener接口
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener()
{
public boolean onDown(MotionEvent e)
{
//手指出品按下的瞬间
return false;
}
public void onShowPress(MotionEvent e)
{
//手指触摸屏幕,并且尚未松开或拖动。与onDown的区别是,onShowPress强调没用松开和没有拖动
}
public boolean onSingleTapUp(MotionEvent e)
{
//手指离开屏幕(单击)
return false;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
//手指按下并拖动,当前正在拖动
return false;
}
public void onLongPress(MotionEvent e)
{
//手指长按事件
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
//手指快速滑动
return false;
}
};
GestureDetector mGestureDetector = new GestureDetector(this,listener);
//防止长按后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
接着,还需要接管目标的onTouchEvent方法,在待监听View的onTouchEvent方法中加入
return mGestureDetector.onTouchEvent(event);
在手势检测中除了OnGestureListener()之外还有OnDoubleTapListener(),它主要处理双击相关的事件,可以通过setOnDoubleTapListener将该监听器设置到GestureDetector中。
6.Scroller
Scroller是一个专门用于处理滚动效果的工具类,可能在大多数情况下,我们直接使用Scroller的场景并不多,但是很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPager、ListView等。而如果能够把Scroller的用法熟练掌握的话,我们自己也可以轻松实现出类似于ViewPager这样的功能。其实Scroller本身是无法让View弹性滑动的,它需要和View的computeScroll方法配合使用才能共同完成这个功能,关于View的滑动具体的内容可以去参考View的滑动。