虽说View并不是属于四大组件,但是他的作用堪比四大组件,甚至可以说比Receiver和Provider的重要性更大.
什么是View
View是Android中所有控件的基类,不管是简单的Button还是复杂的RelativeLayout和ListView,他们的共同基类都是View,所以说,View是一种界面层的控件的一种抽象,它代表了一个控件,除了View还有ViewGroup,从名字上来看,它可以被翻译成控件组,言外之意是ViewGroup内部包含了许多个控件,即一组View。在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件,也可以是由多个控件组成的一组控件,通过这种关系就形成了View树的结构,这和Web前端中的DOM树的概念是相似的,根据这个概念我们知道,Button显然是个View,而LinearLayout不但是一个View,而且还是一个VeiwGroup,而ViewGroup内部是可以有子View的,这个子View同样还可以是ViewGroup,以此类推,所以在XML布局文件中,LinearLayout是可以包裹Button等继承View的控件,也可以包裹LinearLayout等集成ViewGroup等控件!
View的位置参数
View的位置主要是由它的四个顶点来决定的,分别对应于View的四个属性:top(左上角纵坐标),left(左上角横坐标),right(右下角横坐标),bottom(右下角纵坐标),需要注意的是,这些坐标都是相对于View的父容器来说的,因此他是一种相对坐标,View的坐标和父容器的关系如下图,
根据上图,很容易得出View的宽高和坐标的关系
width = right - left
height = bottom - top
那么我们如何得到View的这4个参数?也很简单,在View的源码中他们对应于mLeft,mRight,mTop,mBottom这4个成员变量,获取方式为:
Left = getLeft();
Right = getRight();
Top = getTop();
Bottom = getBottom();
从android3.0开始,View增加了额外的几个参数:x,y,transactionX和transactionY,其中x,y是View左上角的坐标,而transactionX和transactionY是View相对于父容器的偏移量,这几个参数也是相对于父容器的坐标,并且transactionX和transactionY的默认值是0,和View的4个基本参数一样,View也是为他们提供了get/set,这几个参数的换算关系如下:
x = left + translationX
y = top + translationY
需要注意的是,View在平移的过程中,top和left表示的是原始左上角的位置信息,其值并不会发生改变,臭手发生改变得是x,y,translationX和translationY这四个参数.
MotionEvent和TouchSlop
1,MotionEvent
在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:
ACTION_DOWM—–手指刚接触屏幕
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坐标
2,TouchSlop
TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作,
原因很简单:滑动的距离太短,系统不认为他是滑动,这是一个常量,和设备有关,在不同的设备上这个值可能是不同的,通过如下方式可以获取这个常量:
ViewConfiguration.get(getContext()).getScaledTouchSlop().
这个常量有什么意义呢?
当我们处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们可以认为没有达到滑动距离的临界值,因此就可以认为他们不是滑动,这样可以有更好的用户体验,其实如果细心的话,可以在源码中找到这个常量的定义,在frameworks/base/core/res/res/values/config.xml文件中,如下所示,这个”config_viewConfigurationTouchSlop”对应的就是这个常量的定义,
<!--Base"touch shop" value used by ViewConfiguration as a movement threshold where scrolling should begin,-->
<dimen name = "config_viewConfigurationTouchSlop">8dp</demen>
VelocityTracker,GestureDetector和Scroller
1,VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度,他的使用过程很简单,首先,在View的onTouchEvent方法中追踪当前单击事件的速度:
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
接着,当我们想知道当前的滑动速度时,这个时候可以采用如下方式来获得当前的速度:
velocityTracker.computeCurrentVelocity(1000)
int xVelocity = (int)velocityTracker.getXVelocity();
int yVelocity = (int)velocityTracker.getYVelocity();
在这一步中有两点需要注意,
第一点,获取速度之前必须先计算速度,即,getXVelocity和getYVelocity这两个方法的前面必须调用computeCurrentVelocity方法,
第二点,这里的速度是指一段时间内手指所滑动过的像素数,比如将时间间隔为1000ms时,在1s内,手指在水平方向从左向右滑动100像素,那么水平速度就是100,注意速度可以为负数,当手指从右往左滑动时,水平方向速度即为负值,这个需要理解一下,速度的计算可以用如下公式来表示:
速度 = (终点位置 - 起点位置) / 时间段
根据上面的公式在加上android系统中的坐标系,可以知道,手指逆着坐标系的正方向滑动,所产生的速度就为负值,另外,computeCurrentVelocity这个方法的参数表示的是一个时间单位或者说时间间隔,它的单位是毫秒(ms),计算速度时得到的速度就是在这个时间间隔内手指在水平或竖直方向上说滑动的像素数,针对上面的例子,如果我们通过velocityTracker.computeCurrentVelocity(100)来获取速度,那么得到的速度就是手指在100ms内所滑动的像素数,因此水平速度就成了10像素/每100ms(这里假设滑动过程是匀速的),即水平速度为10.这点需要好好理解下
最后,当不需要使用它的时候,需要调用clear方法来重置并回收内存:
velocityTracker.clear();
celocityTracker.recycle();
上面就是如何使用VelicityTracker对象的全过程
GestureDetector
手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为
要使用GestureDetector也不复杂,参考过程如下
首先,需要创建一个GestureDetector对象并实现onGestureListener接口,根据需要我们还可以实现OnDoubleTapListener从而能够监听双击行为:
GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongPressEnabled(false);
接着,接管目标View的onTouchEvent方法,在监听View的onTentEvent方法中添加如下实现:
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;
做完上面两部,我们就可以有选择的是实现OnGestureDetector和OnDoubleTapListener中的方法了,这两个接口中的方法介绍如下表:
方法名 描述 所属接口
onDown 手指触摸屏幕的一瞬间: ACTION_DOWN OnGestureListener
onShowPress 手指轻轻触摸屏幕尚未松开或 拖动的状态 OnGestureListener
onSingleTapUp 手指松开,伴随着1个ACTION_UP而触发,这是单击行为 OnGestureListener
onScroll 手指按下屏幕并拖动,拖动行为 OnGestureListener
onLongPress 长久的按着屏幕不放,即长按 OnGestureListener
onFling 快速滑动后松开 OnGestureListener
onSingleTapConfirmed 严格的单击行为,注意与onSingleTapUp不同,
如果触发了此方法,那么不可能在紧跟着另一个单击行为,
即这只能是单击,不是双击中的一次单击
OnDoubleTapListener
onDoubleTap 双击,由两次连续的单击组成不可能和onSingleTapConfirmed方法共存 OnDoubleTapListener
onDoubleTapEvent 表示发生了双击行为,在双击的期间,ACTION_DOWN/MOVE/UP都会触发此 OnDoubleTapListener
上面表格的方法很多,并不是所有的方法都会被常常用到,在日常开发中,比较常见的有:onSingleTapUp(单击),onFling(快速滑动),onScroll(拖动),onLongPress(长按),onDoubleTap(双击).另外要说明的是,实际开发中可以不使用GestureDetector.完全可以自己在View的onTouchEvent方法中实现所需要的监听,这个就看个人喜好了,这里有一个建议:如果只是滑动相关,建议自己在onTouchEvent中实现,如果要监听双击行为的话,了么就是用GestureDetector.
Scroller
弹性滑动对象,用于实现View的弹性滑动,我们知道,当使用View的scollTo/scrollBy方法来进行滑动时,其过程是瞬间完成的,这个没有过度效果的滑动用户体检是不好的,这个时候就可以使用Scroller来实现过度效果的滑动,其过程不是瞬间完成的,而是在一定的时间间隔内完成,Scroller本身无法让View弹性滑动,他需要和View的computeScroll方法配合使用才能完成这个功能,了么如何使用Scroller?它的典型的代码是固定的:
Scroller scroller = new Scroller(mContext);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000ms内滑向destX,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,delta,0,1000);
invalidate();
}
@override
public void computeScroll(){
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}