Android View滑動相關的基礎知識點

_小馬快跑_發表於2017-12-15

本文涉及到的知識點: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();
     }
 }
複製程式碼

看下效果:

GIF.gif

流程是這樣的:首先在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側滑選單

相關文章