Android View滑动相关的基础知识点
本文涉及到的知识点:MotionEvent、ViewConfiguration、VelocityTracker 、GestureDetector、scrollTo、scrollBy、Scroller、OverScroller
MotionEvent
ACTION_DOWN :手指刚接触到屏幕
ACTION_MOVE :手指在屏幕上移动
ACTION_UP :手指在屏幕上松开的一刹那
ACTION_CANCEL:当前滑动手势被打断
1、如果点击屏幕立即松开,事件顺序 ACTION_DOWN->ACTION_UP
2、如果点击屏幕滑动然后松开,事件顺序 ACTION_DOWN->ACTION_MOVE->ACTION_UP
3、ACTION_CANCEL是当前滑动手势被打断时调用,比如在某个控件保持按下操作,然后手势从控件内部转移到外部,此时控件手势事件被打断,会触发ACTION_CANCEL
MotionEvent类中有两组方法 getX()/getY() 以及getRawX()/getRawY(),由此我们可以得到点击事件的x坐标和y坐标,他们之间不同的是getX()/getY() 返回的是相当于当前View左上角的x和y坐标,getRawX()/getRawY()返回的是相当于手机屏幕左上角的x和y坐标。
ViewConfiguration
主要用到下面三个:
1、getScaledTouchSlop():
ViewConfiguration.get(getContext()).getScaledTouchSlop()返回一个int类型的值,表示被系统认为的滑动的最小距离,小于这个值系统不认为是一次滑动 。
2、getScaledMaximumFlingVelocity()
ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity()获得一个fling手势动作的最小速度值。
3、getScaledMinimumFlingVelocity()
ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity()获得一个fling手势动作的最大速度值。
VelocityTracker
VelocityTracker 是一个跟踪触摸事件滑动速度的帮助类,用于实现flinging 及其他类似的手势,它的使用流程:
*1、通过VelocityTracker.obtain()来获得VelocityTracker 实例,
2、通过velocityTracker.addMovement(event)将用户的滑动事件传给velocityTracker
3、通过velocityTracker.computeCurrentVelocity(int units, float maxVelocity)来开始计算速度,然后调用getXVelocity(int)和getYVelocity(int)得到每个指针id检索速度。*
方法 | 备注 |
---|---|
obtain() | 获得VelocityTracker 实例 |
addMovement(MotionEvent event) | 将滑动事件传给VelocityTracker |
computeCurrentVelocity(int units, float maxVelocity) | 计算速度 参数units是时间,单位是毫秒 maxVelocity是最大滑动速度 |
getXVelocity(int id) | 获得X轴的速度,在使用此方法之前必须先调用computeCurrentVelocity()计算速度 |
getYVelocity(int id) | 获得Y轴的速度,在使用此方法之前必须先调用computeCurrentVelocity()计算速度 |
官网给的一个例子 Tracking Movement:
public class MainActivity extends Activity {
private static final String DEBUG_TAG = "Velocity";
...
private VelocityTracker mVelocityTracker = null;
@Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
switch(action) {
case MotionEvent.ACTION_DOWN:
if(mVelocityTracker == null) {
// Retrieve a new VelocityTracker object to watch the velocity of a motion.
mVelocityTracker = VelocityTracker.obtain();
}
else {
// Reset the velocity tracker back to its initial state.
mVelocityTracker.clear();
}
// Add a user's movement to the tracker.
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
// When you want to determine the velocity, call
// computeCurrentVelocity(). Then call getXVelocity()
// and getYVelocity() to retrieve the velocity for each pointer ID.
mVelocityTracker.computeCurrentVelocity(1000);
// Log velocity of pixels per second
// Best practice to use VelocityTrackerCompat where possible.
Log.d("", "X velocity: " +
VelocityTrackerCompat.getXVelocity(mVelocityTracker,
pointerId));
Log.d("", "Y velocity: " +
VelocityTrackerCompat.getYVelocity(mVelocityTracker,
pointerId));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Return a VelocityTracker object back to be re-used by others.
mVelocityTracker.recycle();
break;
}
return true;
}
}
GestureDetector
GestureDetector相比于OnTouchListener提供了更多的手势操作,GestureDetector类对外提供了两个接口:OnGestureListener,OnDoubleTapListener,还有一个内部类SimpleOnGestureListener
OnGestureListener有下面的几个动作:
按下(onDown): 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
长按(onLongPress): 手指按在持续一段时间,并且没有松开。
滚动(onScroll): 手指在触摸屏上滑动。
按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起(onSingleTapUp):手指离开触摸屏的那一刹那。
官网给的例子:Detecting Common Gestures :
public class MainActivity extends Activity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener{
private static final String DEBUG_TAG = "Gestures";
private GestureDetectorCompat mDetector;
// Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the gesture detector with the
// application context and an implementation of
// GestureDetector.OnGestureListener
mDetector = new GestureDetectorCompat(this,this);
// Set the gesture detector as the double tap
// listener.
mDetector.setOnDoubleTapListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event){
this.mDetector.onTouchEvent(event);
// Be sure to call the superclass implementation
return super.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent event) {
Log.d(DEBUG_TAG,"onDown: " + event.toString());
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());
return true;
}
@Override
public void onLongPress(MotionEvent event) {
Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
Log.d(DEBUG_TAG, "onScroll: " + e1.toString()+e2.toString());
return true;
}
@Override
public void onShowPress(MotionEvent event) {
Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
return true;
}
@Override
public boolean onDoubleTap(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
return true;
}
}
更详细可以看下这两篇博客:
http://blog.csdn.net/xyz_lmn/article/details/16826669
http://blog.csdn.net/hpk1994/article/details/51224228
ScrollTo 、scrollBy
1、ScrollTo(int x, int y)、ScrollBy(int x, int y)
ScrollBy默认也是调用的ScrollTo方法:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX 记录的是View的左边缘和View内容的左边缘之间的距离,mScrollY 记录的是View的上边缘和View内容的上边缘之间的距离
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();
}
}
}
看源码scrollTo(int x, int y)中将x值赋给了mScrollX ,y赋给了mScrollY ,所以scrollTo是相对于View左边缘的绝对滑动,scrollBy是相对滑动,另外要注意的是:scrollTo和scrollBy是对View内容的滑动而不是对View的滑动。
Scroller常用方法:
Scroller类并不会控制View进行滑动,它只是View滑动的辅助类,负责计算View滑动的一系列参数:
方法 | 备注 |
---|---|
getCurrX()、getCurrY() | 距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负 |
getStartX()、getStartY() | 开始滑动时距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负 |
getFinalX()、getFinalY() | 滑动停止时距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负 |
startScroll(int startX, int startY, int dx, int dy) | 开始滑动,默认时间250毫秒,startX、startY为开始位置,左上为正值,右下为负值,dx、dy为要滑动的距离,方向同startX、startY |
startScroll(int startX, int startY, int dx, int dy, int duration) | 作用同上,多了一个滑动持续时间时间duration |
computeScrollOffset() | 当前滑动是否已经完成,true表示已经完成,false表示还未完成。 |
fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY) | 手指在触摸屏上迅速移动,并松开,靠惯性滑动 |
abortAnimation() | 强制结束动画,并滑到最终位置 |
forceFinished(boolean finished) | 是否强制结束动画,true便是强制结束 |
调用Scroller的startScroll()方法并不会有任何滑动行为,这里的滑动指的是View内容的滑动而不是View的滑动,来看startScroll方法的源码:
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
可以看到只是一些赋值操作,没有任何滑动操作,所以如果想让View内容滑动,还需要invalidate()的参与,如:
private Scroller mScroller = new Scroller(context);
...
public void zoomIn() {
// Revert any animation currently in progress
mScroller.forceFinished(true);
// Start scrolling by providing a starting point and
// the distance to travel
mScroller.startScroll(0, 0, 100, 0,3000);
// Invalidate to request a redraw
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// Get current x and y positions
int currX = mScroller.getCurrX();
int currY = mScroller.getCurrY();
scrollTo(currX, currY);
invalidate();
}
}
看下效果:
流程是这样的:首先在startScroll中设置各种滑动信息,这时并没有进行滑动,当调用下面的invalidate时,View会进行重绘,重绘时又会调用View中的computeScroll()方法,View中的computeScroll()是个空实现,需要我们自己实现,在computeScroll()中,我们先通过computeScrollOffset判断是否滑动完成,如果没完成,通过mScroller.getCurrX()、mScroller.getCurrY()得到滑动的位置,然后通过scrollTo(currX, currY)来实现滑动,此时完成了一次滑动,然后调用invalidate()方法继续重绘,继续滑动到新位置,直到滑动完成。
OverScroller
OverScroller大部分API和Scroller是一样的,只是多了一些对滑动到边缘时的处理方法:
方法 | 备注 |
---|---|
isOverScrolled() | 通过fling()方法只会判断是否滑动过界 |
springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) | 回弹效果 |
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) | 手指在触摸屏上迅速移动,并松开,靠惯性滑动 |
notifyHorizontalEdgeReached(int startX, int finalX, int overX) | 通知scroller已经在X轴方向到达边界 |
notifyVerticalEdgeReached(int startY, int finalY, int overY) | 通知scroller已经在Y轴方向到达边界 |
OverScroller因为和Scroller非常类似,而且增加了回弹支持,所以大部分情况下我们都可以使用OverScroller。
下一篇通过模仿一下QQ侧滑菜单例子来实践一下上述知识点:Android仿QQ侧滑菜单