1. 程式人生 > >【Android View事件(四)】View滑動與實現滑動的幾種方法

【Android View事件(四)】View滑動與實現滑動的幾種方法

1 前言

在前面的幾篇文章,我向大家介紹的都是單一View事件,而在這篇文章中,我將向大家介紹連續的事件 —— 滑動。
在安卓裝置上滑動幾乎是應用的標配,由於安卓手機螢幕較小,為了給使用者呈現更多的內容,就需要使用滑動來隱藏和顯示一些內容。學習View的滑動對於自定義控制元件的掌握、日常滑動衝突的處理都有很多裨益。為了很好的理解滑動事件,掌握Android座標系與觸控事件就變得格外重要,在此強烈建議先閱讀上面的提到的幾篇相關文章打好基礎。

2 View滑動產生的原理

從原理上講View滑動的本質就是隨著手指的運動不斷地改變座標。當觸控事件傳到View時,系統記下觸控點的座標,手指移動時系統記下移動後的觸控的座標並算出偏移量,並通過偏移量來修改View的座標,不斷的重複這樣的過程,從而實現滑動過程。

3 實現滑動的7種方法

在學習Android座標系和觸控事件之後我們就可以看看系統為開發者提供了那些方法來實現滑動效果吧!

3.0 程式碼例項介紹

為了讓大家更好的理解過程,設計如下程式碼例項

程式碼結構

主要佈局

CustomView具體結構

MainActivity中沒有任何改動,而CustomView只是繼承了View,並重寫了 onToucnEvent()方法。

可以看到程式碼很簡單,這篇文章中我就不提供專案地址了。

3.1 layout 方法

在View繪製的時候,系統都會呼叫layout(int l, int t, int r, int b)方法來確定View的具體位置。系統既然是這樣來設定View的位置的,那麼我們也可以通過呼叫layout(int l, int t, int r, int b)`方法修改left,top,right,bottom這四個屬性來控制View的位置。

    // 檢視座標方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸控點座標
                lastX = x;
                lastY = y;
                break
; case MotionEvent.ACTION_MOVE: // 計算偏移量 int offsetX = x - lastX; int offsetY = y - lastY; // 在當前left、top、right、bottom的基礎上加上偏移量 layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); break; } return true; }

當然使用 getX()getY()方法和使用getRawX()geRawtY()的效果是一樣的,只不過前者使用的是相對位置,而後者使用的是絕對位置。
但是要注意,在使用絕對座標系的時候,每次執行完 ACTION_MOVE的邏輯後,一定要重新設定初始座標,這樣才能獲得準確的偏移量。

     case MotionEvent.ACTION_MOVE:
                // 計算偏移量
              …………

              //重新設定初始座標
              x = (int)(event.getRawX());
              y = (int)(event.getRawY());
                break;

3.2 offsetLeftAndRight() 與 offsetTopAndroidBottom()

    // 檢視座標方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸控點座標
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY
                //同時對於left 和 right進行偏移
                offsetLeftAndRight(offsetX);
                //同時對於top 和 bottom進行偏移
                offsetTopAndBottom(offsetY);
                break;
        }
        return true;
    }

3.3 LayoutParams

LayoutParams類是子View向父View傳遞位置意圖的橋樑,告訴父View他想要變成的樣子。所以我們可以通過LayoutParams中的引數來改變View的位置。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int startX = (int) event.getX();
        int startY = (int) event.getY();
        int lastX = 0;
        int lastY = 0;

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = startX;
                lastY = startY;
                Log.v(TAG, "startX   " + startX + "    startY      " + startY);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.v(TAG, "offsetX ---  " + startX + "    offsetY ---     " + startY);
                int offsetX = startX - lastX;
                int offsetY = startY - lastY;
                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        //註明消費此事件,不然無效果
        return true;
    }

注意:

在獲得 LayoutParams物件的時候,需要將其轉換成直接父View(這個View的上一層佈局)的型別,不然會報錯。
例如,將

    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();

改為錯誤的:

    RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();

報錯如下:

3.4 scrollTo與ScrollBy

在View中系統為開發者提供了兩個關於移動的方法: scrollTo 與 scroolBy。其實這兩個方法非常好理解,單從字面上的意思就知道

scrollTo(x,y) : 移動到 (x,y) 這個座標點
scrlollBy(dx,dy) : 移動的增量為 dx,dy。

我們對於原有程式碼進行如下更改

但是,當我們拖動View的時候卻沒有移動!!!這是為什麼呢?
其實View是移動了地,只不過和我們設想的結果不同。scrollTo()、scrollBy()表示的是 移動當前ViewGroup的所有子View,如果在View中使用,那麼移動的就是View的內容(content)。例如TextView它的content就是文字。這就解釋了為什麼我們的程式碼看不到效果了。

將原有程式碼更改為如下所示:

  ((ViewGroup) getParent()).scrollBy(offsetX, offsetY);

這回的確是動了,但是他卻在亂動。並不是像我們想象中的那樣跟隨著手指的移動而移動。

導致這個的原因是什麼呢? 答案請見《Scroll類原始碼分析與應用》第一節 scrollTo與ScrollBy

3.5 Scroller

關於屬性動畫、ViewDragHelper

二者也是實現View滑動的良好辦法,但是他們都有一定的複雜性直接展開不僅顯得突兀、而且篇幅較大不利於學習,所以屬性動畫與ViewDragHelper我都會以單獨的篇幅展開,方便同學們更好的理解與學習,敬請期待