View的事件体系

Android屏幕坐标系如下图:
http://www.stormzhang.com/android/2013/07/14/android-ontouchlistener-and-ongesturelistener/
四、View的事件体系_第1张图片
(1)MotionEvent中 e1是手指第一次按上屏幕的起点,e2是抬起手指离开屏幕的终点,根据上图Android屏幕坐标系可知:

手指向右滑动,终点(e2)在起点(e1)的右侧,有e2.getX() - e1.getX() 大于0
手指向左滑动,终点(e2)在起点(e1)的左侧,有e2.getX() - e1.getX() 小于0
手指向下滑动,终点(e2)在起点(e1)的下侧,有e2.getY() - e1.getY() 大于0
手指向上滑动,终点(e2)在起点(e1)的上侧,有e2.getY() - e1.getY() 小于0

(2)onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)

distanceX,是前后两次call的X距离,不是e2与e1的水平距离 distanceX,是前后两次call的Y距离,不是e2与e1的垂直距离 具体数值的方向,请详见上图(中)

(3)onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)

velocityX,是X轴的每秒速度 velocityY,是Y轴的每秒速度 具体数值的方向,请详见上图(右) 仔细观察可以发现:velocityX、velocityY的方向与distanceX、distanceY方向正好相反

1.View基础知识

(1)View的位置参数
四、View的事件体系_第2张图片
View的位置由它的四个顶点来决定:
top是左上角纵坐标,
left是左上角横坐标,
right是右下角横坐标,
bottom是右下角纵坐标
这些坐标都是相对View的父容器来说的,因此是相对坐标。
在Android中,x轴和y轴的正方向分别为右和下。

width = right - left;
height = bottom - top;

如何得到这个四个参数:在View源码中对应为mLeft,mRight,mTop,mBottom
这四个成员变量,获取方式:
Left = getLeft;
Right = getRight;
Top = getTop();
Bottom = getBottom();

从Android 3.0开始,View额外增加了几个参数,x,y,translationX和
translationY。
x,y:view左上角坐标
translationX,translationY:左上角相对于父容器的偏移量
这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0。
x = left + translationX
y = top + translationY
需要注意的是在View的平移过程中,top和left表示的是原始左上角的位置信息,其值不会发生变化,此时发生变化的是x,y,translationX,translationY。

例子:

10-09 23:15:01.950: D/TAG(1283): Left=40
10-09 23:15:01.950: D/TAG(1283): Right=189
10-09 23:15:01.950: D/TAG(1283): Top=40
10-09 23:15:01.950: D/TAG(1283): Bottom=78
10-09 23:15:01.950: D/TAG(1283): x=40.0
10-09 23:15:01.950: D/TAG(1283): y=40.0
10-09 23:15:01.950: D/TAG(1283): translationX=0.0
10-09 23:15:01.950: D/TAG(1283): translationY=0.0

一开始的时候,x = left,y = top,如果此时移动TextView,
那么,left,top不变,translationX,translationY发生变化,
从而x和y发生变化。
x,y始终表示当前view的左上角的坐标,是会变化的。

(2)MotionEvent和TouchSlop
MotionEvent:
在手指接触屏幕后所产生的一系列事件中,典型的有下面几种:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上面移动
ACTION_UP:手指从屏幕上松开的一瞬间
正常情况下,一次手指触摸的行为会触发一系列的点击事件,
点击屏幕后离开松开,事件序列为DOWN–>UP;
点击屏幕滑动一会再松开,事件序列为DOWN->MOVE->…->MOVE->UP;
通过MotionEvent对象我们可以得的点击事件发生的x和y坐标,系统提供了两组方法,getX,getY,返回当前点击的x,y坐标(以当前view为参考的),getRawX,getRawY,返回的是点击的位置相对于手机屏幕左上角的x和y坐标。
如上面的TextView的一次点击情况如下:
10-13 23:43:59.566: D/TAG(1255): x=2.9440842
10-13 23:43:59.566: D/TAG(1255): y=17.840744
10-13 23:43:59.566: D/TAG(1255): rawX=42.944084
10-13 23:43:59.566: D/TAG(1255): rawY=203.84074

TouchSlop:
系统所能识别的被认为是滑动的最小距离。
ViewConfiguration. get(getContext()).getScaledTouchSlop();

(3)VelocityTracker,GestureDetector,Scroller
VelocityTracker:
速度追踪,用于追踪手指在滑动过程中的速度,包括水平方向和竖直方向的速度。
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
当想知道当前的速度的时候,
velocityTracker.computeCurrentVelocity(1000);
int xVelocity=(int)velocityTracke.getXVelocity();
int yVelocity=(int)velocityTracke.getYVelocity();
注意,获取速度前必须先计算,就是computeCurrentVelocity方法,速度指手指在一段时间所滑过的像素。

从右往左滑动为负,从下往上滑动为负。也就是逆着坐标系为负。

不需要使用的时候,
velocityTracke.clear();
velocityTracke.recycle();

GestureDetector:
手势检测,用户辅助检测用户的单击,滑动,长按,双击等行为。
创建对象:
GestureDetector mGestureDetector = new GestureDetector(this);
// 解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);
接着,接管目标view的onTouchEvent方法,在onTouchEvent里面
boolean consume= mGestureDetector.onTouchEvent(event);
return consume;

然后就可以选择实现onGestureListener和onDoubleTabListener里面
的方法了。

四、View的事件体系_第3张图片

里面的方法很多,但是并不是所有的方法都会被时常用到,常用的有:
onSingleTapUp(单击),onFling(快速滑动),onScroll(拖动),
onLongPress(长按),onDoubleTap(双击)。

实际的开发中,可以不使用GestureDetector,完全可以在onTouchEvent
里面实现所需要的监听。

建议:
如果只是监听滑动相关的,建议自己在onTouchEvent里面实现,如果需要监听双击这种行为的话,就使用GestureDetector。

Scroller:
弹性滑动对象,当使用View的scrollTo/scrollBy方法进行滑动的时候,其过程是
瞬时完成的,这个没有过度效果的滑动,用户体验不好。
Scroller滑动不是瞬时完成的,而是在一定的时间断内完成的,Scroller本身无法
让view弹性滑动,需要和view的comuteScroll方法配合使用。
典型代码如下:

      @Override      public void computeScroll() {             if ( mScroller != null) {                   if ( mScroller.computeScrollOffset()) {                        scrollTo( mScroller.getCurrX(), mScroller.getCurrY());                        postInvalidate(); // So we draw agai                        ViewCompat.postInvalidateOnAnimation(this);                                           }            }      }
      private void smoothScrollTo(int destX, int destY) {             int scrollX = getScrollX();             int delta = destX - scrollX;             // 1000ms内滑向destX,效果就是慢慢滑动             mScroller.startScroll( scrollX, 0, delta, 0, 1000);            invalidate();      }

2.View的滑动

三种方法:
view本身提供的scrollTo/scrollBy方法;
通过动画给view添加平移效果来实现;
通过改变view的LayoutParams使得view重新布局。
(1)使用scrollTo/scrollBy
这两个方法的实现:

    /** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */    public void scrollTo( int x, int y) {        if (mScrollX != x || mScrollY != y) {            int oldX = mScrollX;            int oldY = mScrollY;            mScrollX = x;            mScrollY = y;            invalidateParentCaches();            onScrollChanged( mScrollX, mScrollY, oldX, oldY);            if (!awakenScrollBars()) {                postInvalidateOnAnimation();            }        }    }    /** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */    public void scrollBy( int x, int y) {        scrollTo( mScrollX + x, mScrollY + y);    }

从中可以看出,scrollBy实际上也是调用scrollTo方法,它实现了基于当前位置的相对滑动,scrollTo实现了基于所传递参数的绝对滑动。

四、View的事件体系_第4张图片

mScrollX和mScrollY的改变规则:
1.这两个属性可以通过getScrollX和getScrollY得到;
2.mScrollX的值总是等于view左边缘和view内容左边缘在水平方向上的距离;
3.mScrollY的值总是等于view上边缘和view内容上边缘在竖直方向上的距离;
4.view边缘指view的位置,由四个顶点组成,view内容边缘指view中内容的边
缘,scrollTo和scrollBy只改变view内容的位置,而不能改变view在布局中的位置。
5.mScrollX和mScrollY的单位为像素,并且当view的左边缘在view内容的左边缘
的右边时,mScrollX为正值,反之为负值;
6.当view的上边缘在view内容的上边缘的下边时,mScrollX为正值,反之为负值。

也就是说:
从左向右滑动,mScrollX为负值,从上往下滑动mScrollY为负值。

(2)使用动画
采用view动画,将一个view在100ms内,从原位置向右下角移动100个像素。

<? xml version= "1.0" encoding= "utf-8" ?>< set xmlns:android= "http://schemas.android.com/apk/res/android"    android:fillAfter= "true"    android:zAdjustment= "normal" >    <translate        android:duration= "100"        android:fromXDelta= "0"        android:fromYDelta= "0"        android:interpolator= "@android:anim/linear_interpolator"        android:toXDelta= "100"        android:toYDelta= "100" /></ set>

采用属性动画,将view在100ms内,从原位置向右移动100个像素。

ObjectAnimator. ofFloat( targetView, "translationX", 0, 100)             .setDuration(1000).start();

view动画无法改变view的位置参数,包括宽高,是对view的影像做操作。
view动画如果希望动画完成之后保持状态需要设置fillAfter为true。

在3.0以下的手机上面通过nineoldandroids来实现的属性动画本质上还是view
动画。

(3)改变布局参数

MarginLayoutParams params = ( MarginLayoutParams) mButton1.getLayoutParams();params.width += 100;params.leftMargin += 100;mButton.requestLayout();//或者 mButton.setLayoutParams( params);

(4)三种滑动方式的对比
scrollTo/ScrollBy优点:
可以比较方便的实现滑动效果,而且不影响内部元素的点击事件。
缺点:
只能滑动view的内容,不能滑动view本身。

动画优点:
复杂效果必须通过动画才能实现。
缺点:
3.0以上使用属性动画,没有明显缺点,但是使用view动画或者3.0以下使用属性动画,均不能改变view本身的属性。实际使用过程中如果动画元素不需要响应用户的交互,那么使用动画来做滑动是比较合适的,否则不太合适。(其实现在3.0以下的也不用怎么考虑)

改变布局:
除了使用麻烦点没有明显缺点,适用对象就是一些具有交互性的view,可以使用改变布局参数的方式实现。

总结:
scrollTo/ScrollBy:操作简单,适合对view的内容滑动。

动画:操作简单,实现复杂的动画效果。

改变布局参数:操作略微复杂,适用于有交互性的view。

有续篇。。。

更多相关文章

  1. Android和蓝牙GPS结合的方法
  2. [ ]在Android系统上使用busybox——最简单的方法
  3. Android切换Activity时的淡入动画和缩小动画
  4. Android中的文件的读取方法
  5. Android设备开机后自动启动APP解决方法:(学习篇)
  6. Android 文字自动滚动(跑马灯)效果的两种实现方法[特别好使]
  7. Android Fragment 生命周期及回调方法

随机推荐

  1. Android中的资源与国际化!
  2. 如何降低android应用程序的耗电量
  3. Android(安卓)JIT带来的虚拟机崩溃问题及
  4. Android(安卓)匿名共享内存C++接口分析
  5. 开始在Windows上开发Android
  6. Android中日期操作总结
  7. 转载 Android(安卓)脚本设计之 SL4A
  8. conversion to dalvik format failed wit
  9. 有关Android(安卓)SQLite编程的一些知识
  10. Mac OS X 下编译android源码