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