【Android View事件(四)】多種花樣實現 View 滑動效果

【大聖代的技術專欄 http://blog.csdn.net/qq_23191031 轉載煩請註明出處,尊重他人勞動成功就是對您自己的尊重】

相關文章
詳解Android控件體系與常用座標系
Android常用觸控類分析:MotionEvent 、 ViewConfiguration、VelocityTracker
Android 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

請見《Scroll類源碼分析與應用》 第二節Scroller

關於屬性動畫、ViewDragHelper

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章