本文涉及到的知識點: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側滑菜單