View的坐标系和位置参数
Android中,view的坐标体系是相对于父View而言的
getTop(); //获取子View初始位置左上角距父View顶部的距离
getLeft(); //获取子View初始位置左上角距父View左侧的距离
getBottom(); //获取子View初始位置右下角距父View顶部的距离
getRight(); //获取子View初始位置右下角距父View左侧的距离
在MotionEvent中,获取触摸点的位置时,Event中除了get的一系列方法之外,还有一个getRaw系列,它是相对于屏幕坐标系来说的。
从Anroid3.0开始,View又增加了几个额外的参数,x,y,translationX 和 translationY。
View.getX() //获取View当前位置左侧距离父View左侧的距离
View.getY() //获取View当前位置顶部侧距离父View顶部的距离
View.getTranslationX() //View在X方向的偏移量,即当前位置相对于初始位置的滑动距离,默认值为 0,左滑为负值
View.getTranslationY() //View在Y方向的偏移量,即当前位置相对于初始位置的滑动距离,默认值为 0,左滑为负值
所以,getX() =getLeft() + getTranslationX() 。如果View的位置不发生改变,getX = getLeft,getTranslationX = 0;View通过滑动(比如动画)改变位置的时候,发生改变的只有x,y,translationX 和 translationY。
另外,通过以上方法所获取到的值,单位都是像素(px)。
参考下图:
View事件的相关概念
MotionEvent
MotionEvent,翻译过来就是动作事件,在Android中用它来接收和处理屏幕上的各种触摸动作。在手指触摸屏幕所产生的一系列事件中,最典型的有以下几种:
- ACTION_DOWN 手指接触屏幕
- ACTION_MOVE 手指在屏幕上移动
- ACTION_UP 手指抬起
结合实际情况,可能会有以下几种结果:
ACTION_DOWN -> ACTION_UP 点击屏幕后松开
ACTION_DOWN -> ACTION_MOVE ->...->ACTION_MOVE ->ACTION_UP 点击滑动,最后松开
这两种情况就是两个完整的事件流。
为了处理点击事件,MotionEvent提供了一组获取点击坐标的方法: getX/RawX,getY/getRawY。
getX/Y:获取点击位置相对于当前View左上角的坐标
getRawX/Y:获取点击位置相对于屏幕左上角的坐标。这里跟View是不一样的,它没有父View的概念。
关于MotionEvent的详细介绍,可以参考这位同学的文章;
TouchSlop
ToucSlop是指系统所能识别出的最小滑动距离,我们可以用它来做一些过滤动作,小于这个值的就不被认为是滑动。这是一个与设备相关的常量,可以通过以下方法获取:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
VelocityTracker
VelocityTracker是速度追踪的意思,顾名思义,它就是用来追踪滑动过程中的速度。使用方法如下:
首先在View的onTouchEvent方法中获取对象,对添加对事件的追踪:
VelocityTracker tracker = VelocityTracker.obtain();
tracker.addMovement(event);
然后设置速度的单位,这里的单位是指像素与时间的关系。比如我们将单位设置为1000(1s),在这1s内划过的像素数为1000,得到的结果并不是1 px/ms,而是1000,它指的是在整个时间段内划过的像素数。代码如下:
tracker.computeCurrentVelocity(1000);
int xVelocity = (int) tracker.getXVelocity();
int yVelocity = (int) tracker.getYVelocity();
GestureDector
手势检测,用于辅助检测用户的动作行为,比如单击,滑动,长按等等。它是和onTouchEvent来配合使用的。当处理比较复杂的事件时,只用onTouchEvent可能会显得非常麻烦,这时候就可以将事件托管给GestureDector来处理。
GestureDector提供了两个接口:
GestureDetector.OnGestureListener 和 GestureDetector.OnDoubleTapListener,分别提供了针对不同事件的方法。
OnGestureListener :
// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
public boolean onDown(MotionEvent e) {
Log.i("MyGesture", "onDown");
Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();
return false;
}
/\*
用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
注意和onDown()的区别,强调的是没有松开或者拖动的状态
而onDown也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,
也就是说当用户点击的时候,首先MotionEventACTION_DOWN,onDown就会执行,
如果在按下的瞬间没有松开或者是拖动的时候onShowPress就会执行,如果是按下的时间超过瞬间
拖动了,就不执行onShowPress。
\*/
public void onShowPress(MotionEvent e) {
Log.i("MyGesture", "onShowPress");
Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT).show();
}
// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
///轻击一下屏幕,立刻抬起来,才会有这个触发
//从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以这个事件 就不再响应
public boolean onSingleTapUp(MotionEvent e) {
Log.i("MyGesture", "onSingleTapUp");
Toast.makeText(MainActivity.this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
return true;
}
// 用户按下触摸屏,并拖动,由1个ACTION_DOWN, 多个ACTION_MOVE触发
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("MyGesture22", "onScroll:"+(e2.getX()-e1.getX()) +" "+distanceX);
Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG).show();
return true;
}
// 用户长按触摸屏
public void onLongPress(MotionEvent e) {
Log.i("MyGesture", "onLongPress");
Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG).show();
}
// 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发,这是快速滑动行为
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
Log.i("MyGesture", "onFling");
Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG).show();
return true;
}
OnDoubleTapListener:
/\*
用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,
系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,
然后触发SingleTapConfirmed事件。只要触发该事件,那么当前事件一定是单击而不是双击中的一次。
\*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
/\*双击事件
\*/
@Override
public boolean onDoubleTap(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
/\*
双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件
@MotionEvent e中包含了双击直接发生的其他动作.
\*/
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
除此之外,GestureDetector还提供了一个简单的内部类,SimpleOnGestureListener,基本就是将两个listener拼到了一起。
GestureDetector使用起来也非常简单,首先要获取GestureDetector对象,然后在View的onTouchEvent方法中接管Event。代码如下:
GestureDetector mGestureDetector = new GestureDetector(this);//当前class继承了GestureDetector的接口
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.setIsLongpressEnabled(false);//解决长按后屏幕无法拖动
return mGestureDetector.onTouchEvent(event);
}
View的滑动
View的滑动是各种控件的基础,比如ViewPager,SlidingMenu,下拉刷新等等,他们的基础都是滑动。通常来说,View的滑动有以下三种方式:
scrollTo/By(int x, int y);
scrollBy是相对上一个位置的滑动,x,y为滑动的像素距离,负值表示向下或右,scrollTo表示绝对滑动。简单来说,scrollTo表示相对于View的初始位置滑动x,y个像素,而scrollBy是相对于上一个位置滑动x,y个像素。使用动画滑动
举个简单例子:
ObjectAnimator.ofFloat(imageView,"translationX",-30f).setDuration(1000).start();
表示在1s内,将imageView沿X方向向左滑动30个像素。
- 改变布局参数
这个很好理解,直接上代码:
//MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
LayoutParams params = (LayoutParams) mButton.getLayoutParams();
params.width += 100;
mButton.requestLayout();
//mButton.setLayoutParams(params);
注意:scrollTo/By(int x, int y),采用该方法进行滑动,滑动的只是View的内容,而View的位置不发生变化。比如将一个button通过scrollTo/By(int x, int y)移动到新的位置之后,点击当前button所处的位置是没有反应的,因为View实际上仍然处于原始位置。
这里只是简单介绍了View的滑动方法,关于View的弹性滑动,可以参考这篇博文.