Android 坐標系和 MotionEvent 分析、滑動


1.Android坐標系

在Android中,屏幕最左上角的頂點作為Android坐標系的原點,這個點向左是X軸正方向,這個點向下是Y軸正方向。

系統提供了getLocationOnScreen(int location[])這樣的方法來獲得Android坐標系中中點的位置(即該圖的左上角在Android坐標系中的坐標)。另外,觸控事件中使用getRawX() 、getRawY()方法所獲得的坐標同樣是Android坐標系中的坐標

2.視圖坐標系
Android除了上面說的坐標系之外,還有一個視圖坐標系,它描述了子視圖在父視圖中的位置關系。這兩種坐標系並不矛盾也不復雜,他們的作用是相輔相成的,正方向的指向是相同的,只是原點的位置不再是Android坐標系中的屏幕最左上角,而是以父視圖左上角為坐標原點。在觸控事件中,通過getX()、getY()坐標就是視圖坐標系中的坐標

3.觸控事件—MotionEvent
觸控事件MotionEvent在用戶交互中,占據着舉足輕重的地位,學好觸控事件是掌握后續內容的基礎,首先我們先看看MotionEvent中封裝的一些常用的常量,它定義了觸控事件的不同類型。


    /** 單點觸摸按下操作*/
    public static final int ACTION_DOWN             = 0;
    
    /** 單點觸摸離開操作*/
    public static final int ACTION_UP               = 1;
    
    /** 觸摸點移動操作*/
    public static final int ACTION_MOVE             = 2;
    
    /** 觸摸操作取消*/
    public static final int ACTION_CANCEL           = 3;
    
    /** 觸摸操作超出邊界 */
    public static final int ACTION_OUTSIDE          = 4;

    /** 多點觸摸按下操作*/
    public static final int ACTION_POINTER_DOWN     = 5;
    
    /** 多點離開操作*/
    public static final int ACTION_POINTER_UP       = 6;

通常情況下,我們會在onTouchEvent(MotionEvent event)方法中通過event.getAction() 方法來獲取觸控事件的類型並通過switch-case方法來篩選,這代碼的模式基本固定,如下:

@Overrride
public boolean onTouchEvent(MotionEvent event) {

    // 獲取當前輸入點的X,Y坐標(視圖坐標)    
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            // 處理輸入的按下事件
            break; 
     
        case MotionEvent.ACTION_MOVE:
            // 處理輸入的移動事件
            break; 

        case MotionEvent.ACTION_UP:
            // 處理輸入的離開事件
            break;        

    }
    return true;
}

這個代碼模板是不涉及多點操作的,基本上可以幫助完成常見的觸控操作的監聽。

下面圖示可以幫助你了解測量時的API如何使用它們,並幫助對MotionEvent和Android坐標系有了一個比較清楚的認識。

4.七種滑動方法
(1).layout方法
簡單的來說就是,記錄觸摸的初始值和偏移量,然后通過layout方法移動控件。
(2).offsetLeftAndRight()與offsetTopAndBottom()
這個是系統提供的一個對上下、左右移動的API的封裝。當計算出來偏移量后,需要使用下面的代碼就可以重新布局,效果和使用Layout一樣,代碼如下。

// 同時對left和right進行偏移 
offsetLeftAndRight(offsetX);
// 同時對top和bottom進行偏移
offsetTopAndBottom(offsetY);

(3).LayoutParams
LayoutParams保存了一個View的布局參數。我們可以改變LayoutParams來動態修改一個布局的位置參數,從而來達到改變View位置的效果。我們可以很方便的在程序中使用getLayoutParams()來獲取一個View的LayoutParams。獲取到偏移量后就可以通過setLayoutParams()來改變其LayoutParams。不過,需要注意的是LayoutParams的類型問題,要根據父容器的類型來設置不同的類型的LayoutParams。如果不清楚父類容器是什么,可以使用ViewGroup.MarginLayoutParams。

(4).scrollTo 與 scrollBy
這兩個方法是view提供的方法來改變一個View的位置,scrollTo(x,y)表示移動到一個具體的坐標點(x,y),而scrollBy(dx, dy)表示移動的增量為dx,dy。需要注意的是,在View上面執行這兩個方法,移動的並不是View本身而是View的內容,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移動的是所有的子View,如果在View中使用,那么移動的將是View的內容,例如TextView,內容就是它的文本,ImageView,內容就是它的drawable對象。

通過上面的分析,我們就應該知道為什么不能在View中使用這兩個方法來拖動這個View了。那么我們就要在View所在的ViewGroup中來使用scrollBy方法,移動它的子View,代碼如下所示:

((View) getParent.getScrollBy(offsetX, offsetY);

但是因為參考系的問題,在真正操作的時候,如果需要根據拖動來滑動,則需要做的事情就是在計算參數時需要做轉負值操作:

int offsetX = x - lastX;
int offsetY = y - lastY;
((View)getParent.scrollBy(-offsetX, -offsetY);

通過查看下面的圖就可以了解到為何要做這個處理。

(5).Scroller

Scroller類提供了自然過度動畫來實現移動效果,即實現平滑移動的效果,而不再是瞬間完成的移動。下面我們會演示一下如何使用Scroller類實現平滑移動。需求是:子View跟隨手指的滑動而滑動,但是在手指離開屏幕時,讓子View平滑的移動到初始位置,即屏幕左上角。

使用Scroller類需要如下三個步驟:

  • 初始化Scroll
    首先要通過它的構造方法來創建一個Scroller對象:
// 初始化Scroller
mScroller = new Scroller(context);
  • 重寫computeScroll()方法,實現模擬滑動
    下面我們需要重寫computeScroll(),它是使用Scroller的核心,系統在繪制view的時候回在draw()方法中調用該方法。這個方法實際上就是使用的ScrollTo方法。再結合Scroller對象,幫助獲取到當前的滾動值。我們可以通過不斷地瞬間移動一個小的距離來實現整體上的平滑移動效果。computeScroll的代碼可以利用如下模版代碼來實現:
 @Override
    public void computeScroll() {
        super.computeScroll();
        // 判斷Scroller是否執行完畢
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            // 通過重繪來不斷調用computeScroll
            invalidate();
        }
    }

Scroller 類提供了computeScrollOffset()方法來判斷是否完成了整個滑動,同時也提供了getCurrX(),getCurrY()方法來獲得當前的滑動坐標。在上面的代碼中,唯一需要注意的是invalidate()方法,因為只能在computeScroll()方法中獲取模擬過程中的scrollX和scrollY坐標,但是computeScroll是不會自動調用的,只能通過invalidate() --》draw()--》computeScroll()來間接調用computeScroll方法,實現循環獲取scrollX和scrollY的目的。當模擬過程結束后,scroll.computeScrollOffset()方法會返回fasle,從而中斷循環,完成整個平滑移動過程。

  • startScroll開啟模擬過程

startScroll()方法具有兩個重載方法:

 public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }

 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        
    }

在使用的時候,dx 和 dy的正負和上面scrollTo 與 scrollBy的方法的一樣。代碼如下:

case MotionEvent.ACTION_UP:
    // 手指離開時,執行滑動過程
    View viewGroup = ((View) getParent());
    mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
    invalidate();
    break;

6.屬性動畫
這個目前不做過多的介紹。

7.ViewDragHelper
這個功能很強大,可以先看一下QQ的側邊欄滑動,然后再總結這塊的內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM