View不是四大组件之一,但重要性堪比四大组件,本篇博文主要讲解View的事件体系,包括View的基础知识,滑动,弹性滑动,事件分发机制,滑动冲突的种类与解决方案。
(1).View 的位置参数
View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top,left,right,bottom。从android3.0开始 View增加了额外的几个参数:x,y,translationX,和translationY。
x= left + translationX;
y=top +translationY;
(2).MotionEvent 和 TouchSlop
1.MotionEvent
在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:
A.ACTION_DOWN : 手指刚接触屏幕
B.ACTION_MOVE : 手指在屏幕上移动
C.ACTION_UP : 手指离开屏幕的一瞬间
正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,例如:
A. 点击屏幕后离开,事件序列为DOWN->UP;
B. 点击屏幕滑动一会松开,事件序列为DOWN->MOVE->...->MOVE->UP;
通过MotionEvent的对象我们可以得到点击事件发生的x和y坐标。因此,系统系统提供了两组方法:getX/getY和getRawX/getRawY。它们的区别很简单,getX和getY返回的是相当于当前View左上角的x和y坐标,而getRawX和getRawY返回的是相对于手机屏幕左上角的x和y坐标。
2.TouchSlop
TouchSlop是系统所能识别出的被认为是滑动的最小距离。当我们在处理滑动时,就可以利用这个常量做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未滑动。
3.VelocityTracker,GestureDetector 和Scroller
a) VelocityTracker是速度追踪,用户追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。他的使用很简单,首先,在View的onTouchEvent方法中追踪当前单机事件的速度:
VelocityTracker vt=VelocityTracker.obtain();
vt.addMovement(event);
接着就想知道当前的滑动速度时,就可以采用这个方式来获得速度:
vt.computeurrentVelocity(100);//100表示时间间隔为100 ms
Int x=(int)vt.getXVelovity();
Int y=(int)vt.getYVelovity();
当不需要使用它的时候,用clear方法来重置回收内存:
vt.clear();
vt.recycle();
b) GestureDetector
手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为。
首先,我们创建一个GestureDetector 对象并实现onGestureListener接口,根据需要我们还可以实现onDoubleTapListener从而能够监听双击行为:
GestureDetector mGestureDetector =new GestureDetector(this);
//解决长按屏幕无法拖动的现象
mGestureDetector.setIsLongPressEnable(false);
接着实现目标View的onTouchEvent方法,在待监听的View的onTouchEvent方法中添加如下实现:
Boolean consume=mGestureDetector.onTouchEvent(event);
做完上两步,我们就可以有选择地实现OnGestureListener和OnDoubleTapListenter中的方法了。另外,在实际开发中,我们可以不使用GestureDetector,完全可以自己在View的onTouchEvent方法中实现所需的监听,当需要监听双击这种行为时,再使用GestureDetector.
(c).Scroller
弹性滑动对象,用于实现View的弹性滑动。我们知道,当使用View的scrollTo/scrollBy方法进行滑动时,其过程是可以瞬间完成的,这个没有过渡效果的滑动用户体验不好。这个时候,我们就可以使用Scroller来实现由过渡效果的滑动,其过程不是瞬间完成的,而是在一定时间间隔内完成的。Scroller配合view的computeScroll就能完成这个功能。它的典型代码固定如下:
Scroller scroller =new Scroller(mContext);
Private void smoothScrollTo(int destX,int destY) {
Int scrollX=getScrollX();
Int delta = destX - scrollX;
mSroller.startScroll(scrollX,0delta,0,1000);
Invalidate();
}
@Override
Public void computeScroll(){
If(mSroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX().mScroller.getCurrY());
postInvalidate();
}
}
不管一些滑动效果多么绚丽,归根到底,他们都是由不同的滑动外加一些特效所组成的。因此。掌握滑动的方法是实现绚丽的自定义控件的基础。通过三种方式可以实现View的滑动: 第一种事通过View 本身提供的scrollTo/scrollBy 方法来实现滑动;第二种是通过动画给View施加平移来实现滑动;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。下面,我们一一对其进行讲解。
(1).使用scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能。那就是scrollTo/scrollBy。经过分析源码得知,scrollBy实际上也是调用了scrollTo方法,而scrollTo则实现了基于所传递参数的绝对滑动。利用scrollTo和scrollBy来实现View的滑动,这不困难,但我们要知道。滑动过程中,View内部的两个属性mScrollX和mScrollY的改变规则。这两个属性可以通过getScrollX和getScrollY方法分别得到。
在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向上的距离,而mScrollY的值,总是等于View上边缘和View内容上边缘的竖直方向的距离。scrollTo和scrollBy只能改变View的内容的位置,而不能改变View在布局中的位置。mScrollX和mScrollY的单位是像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值,反之为负值。当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说,如果从左向右滑动,那么mScrollX为负值,反之为正值。如果从上往下滑动,mScrollY为负值,反之为正值。
(2).使用动画
使用动画来移动View,主要操作View的translationX和translationY属性,即可以使用传统的View动画来完成,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0一下版本,需要采用开源动画库nineoldandroids(http://nineoldandroids.com/)
使用传统的View动画,可以xml中的指定translate,采用属性动画,更简单了,比如:
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();
具体更多的动画使用方法,我会接下来的博文中单独列出来一篇讲解动画,这里就先不详述了。
需要注意的一点是,View动画是对View的影像的操作,它并不能真正改变View的位置包括宽/高,造成View无法响应点击事件,因为View的真身还在原来的位置,并且如果希望动画后的状态得以保留。还必须将fillAfter的属性设置为true,否则动画完成后,其动画效果就会消失。
(3).改变布局参数
改变布局参数,即改变LayoutParams比较好理解,比如我们想把一个Button向右平移100px,我们主要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可。
MarginLayoutParams params = (MarginLayoutParams )mButton1.getLayoutParams();
Params.width +=100;
params.leftMargin +=100;
mButton1.requestLayout();
//或者 mButton1.setLayoutParams(params);
(4).各种滑动方式对比
1.scrollTo/scrollBy:操作简单。适合View滑动,滑动后支持响应事件。
2.动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果。推荐使用nineoldandroids兼容android 3.0以下版本。
3.改变布局参数:操作稍微复杂,适用于有交互的Veiw.
如果View生硬的滑动过去,用户体验实在太差,因此。我们要实现渐进式滑动。渐进式滑动有一个共同思想:将依次大的滑动分成若干次小的滑动,并在一个时间段内完成。常用的弹性滑动方式有Scroller,使用动画,还有延时策略。下面我们一一讲解
(1) .使用Scroller
Scroller方法已经分析过了,如果有有兴趣可以分析它的源码。原理如下:
invalidate方法会导致View重绘,在View的draw方法中,又会去调用computeScroll方法。
(2) .通过动画
动画本身就是一种渐进过程,因此通过它来实现滑动天然就具有弹性效果,比如下面代码可以让一个View的内容在100ms内向左移动100ms.
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();不过这里想说的并不是这个问题,我们可以利用动画特性来实现一些动画不能实现的效果。还拿scrollTo来说,我们也想模仿Scroller来实现View的弹性滑动。那么利用动画的特性。我们可以采用如下方式来实现。
Final int startX=0;
Final int deltax=100;
valueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new AnimatorUpdateListener(){
@Override
public void onAnimatorUpdate(ValueAnimator animator){
Float fraction=animator.getAnimatedFraction();
mButton1.scrollTo(startX+(int)(deltaX*fraction),0)
}
});
animator.start();
上述代码,我们的动画本质上没有作用于任何对象上,它只是在1000ms内完成了整个动画过程。利用这个特性,我们就可以在动画的每一帧到来时获取动画完成比例,然后再根据这个比例计算当前View的滑动距离。注意,这里的滑动特性针对的是View的内容而非View本身。它与Scroller比较类似,都是通过改变一根百分比配合scrollTo方法来完成View的滑动。需要说明一点,采用这种方法除了能够完成弹性滑动以外,还可以实现其动动画效果,我们可以在onAnimationUpdate方法中加上我们想要的操作。
(3) .使用延时策略
延时策略的核心思想是通过发送一系列延时消息,从而达到渐进式的效果,具体来说,可以使用Handler 或View的postDelayed方法,也可因使用线程的sleep方法。使用起来比较简单,就不举例子了。
讲了这么多,还都是基础知识,将这篇文章,设为View事件体系的上篇吧,在下篇中,我们再去事件分发机制,滑动冲突的结局方案。