Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方開發文檔Training系列課程中文版:手勢處理之拖拽或縮放

Android官方開發文檔Training系列課程中文版:手勢處理之拖拽或縮放

編輯:關於Android編程

原文地址:https://developer.android.com/training/gestures/scale.html

這節課主要學習如何使用觸摸手勢來拖動、放大屏幕上的對象。

拖動對象

如果你的重點在Android 3.0以上的版本,那麼你可以使用內置的拖拽事件監聽器View.OnDragListener。

觸摸手勢最常見的操作就是使用它來拖動屏幕上的對象。下面的代碼允許用戶拖動屏幕上的圖像。要注意以下幾點:

在拖動操作中,APP會一直保持手指拖動的軌跡,就算是另一只手指觸到屏幕也是。舉個例子,想象一根手指在拖動著一張圖像,這時用戶將第二根手指放置到屏幕上,如果APP只是追蹤單根手指的軌跡,那麼它會將第二根手指作為默認位置,並會將圖像移動到這個位置。 為了防止這樣的事件發生,APP需要區分第一根手指與其它手指。為此,需要追蹤 ACTION_POINTER_DOWN 及 ACTION_POINTER_UP 。ACTION_POINTER_DOWN 及 ACTION_POINTER_UP在第二根手指落下或抬起的時候由onTouchEvent()方法傳回。 在ACTION_POINTER_UP的情況下,示例提取了這個事件的索引,並確保當前活動的指針不是那個已經不在屏幕上的指針。如果是那個指針的話,那麼APP會選擇一個不同的指針使其活動並保存它的X及Y的位置。一旦這個值被保存下來,那麼APP將會使用正確指針的數據一直計算剩余移動的距離。

下面的代碼允許用戶在屏幕上拖動對象。它記錄了當前活動指針的初始位置,計算了它所位移的距離,並將對象移動到新的位置上。

這裡要注意,代碼段使用了getActionMasked()方法。你應該一直使用這個方法來接收MotionEvent對象的活動。與getAction()方法不同,getActionMasked()工作於多點觸控模式下。它會返回被執行的掩飾活動,不包括指針的索引比特。

// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);

    final int action = MotionEventCompat.getActionMasked(ev); 

    switch (action) { 
    case MotionEvent.ACTION_DOWN: {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev); 
        final float x = MotionEventCompat.getX(ev, pointerIndex); 
        final float y = MotionEventCompat.getY(ev, pointerIndex); 

        // Remember where we started (for dragging)
        mLastTouchX = x;
        mLastTouchY = y;
        // Save the ID of this pointer (for dragging)
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        break;
    }

    case MotionEvent.ACTION_MOVE: {
        // Find the index of the active pointer and fetch its position
        final int pointerIndex = 
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);  

        final float x = MotionEventCompat.getX(ev, pointerIndex);
        final float y = MotionEventCompat.getY(ev, pointerIndex);

        // Calculate the distance moved
        final float dx = x - mLastTouchX;
        final float dy = y - mLastTouchY;
        mPosX += dx;
        mPosY += dy;
        invalidate();
        // Remember this touch position for the next move event
        mLastTouchX = x;
        mLastTouchY = y;
        break;
    }

    case MotionEvent.ACTION_UP: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_CANCEL: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_POINTER_UP: {

        final int pointerIndex = MotionEventCompat.getActionIndex(ev); 
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex); 
            mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex); 
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
        break;
    }
    }       
    return true;
}

平移

上面的部分展示了如何在屏幕上拖動對象。另一個通用的場景就是平移了,平移的意思是:用戶的拖動動作引起的x及y軸方向上的滾動。上面的代碼直接將MotionEvent攔截實現拖動。這部分的代碼將會采用另一種更具有優勢的方法,以便支持通用手勢。它重寫了GestureDetector.SimpleOnGestureListener的onScroll()方法。

只有用戶在使用手指移動內容時,onScroll()才會被調用。onScroll()只有在手指按下的時候才會調用,一旦手指離開屏幕,那麼平移手勢也隨之終止。

下面是onScroll()的使用摘要:

// The current viewport. This rectangle represents the currently visible
// chart domain and range.
private RectF mCurrentViewport =
        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);

// The current destination rectangle (in pixel coordinates) into which the
// chart data should be drawn.
private Rect mContentRect;

private final GestureDetector.SimpleOnGestureListener mGestureListener
            = new GestureDetector.SimpleOnGestureListener() {
...

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
    // Scrolling uses math based on the viewport (as opposed to math using pixels).

    // Pixel offset is the offset in screen pixels, while viewport offset is the
    // offset within the current viewport.
    float viewportOffsetX = distanceX * mCurrentViewport.width()
            / mContentRect.width();
    float viewportOffsetY = -distanceY * mCurrentViewport.height()
            / mContentRect.height();
    ...
    // Updates the viewport, refreshes the display.
    setViewportBottomLeft(
            mCurrentViewport.left + viewportOffsetX,
            mCurrentViewport.bottom + viewportOffsetY);
    ...
    return true;
}

下面是setViewportBottomLeft()方法的實現,它主要實現了移動內容的邏輯:

/**
 * Sets the current viewport (defined by mCurrentViewport) to the given
 * X and Y positions. Note that the Y value represents the topmost pixel position,
 * and thus the bottom of the mCurrentViewport rectangle.
 */
private void setViewportBottomLeft(float x, float y) {
    /*
     * Constrains within the scroll range. The scroll range is simply the viewport
     * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the
     * extremes were 0 and 10, and the viewport size was 2, the scroll range would
     * be 0 to 8.
     */

    float curWidth = mCurrentViewport.width();
    float curHeight = mCurrentViewport.height();
    x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
    y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));

    mCurrentViewport.set(x, y - curHeight, x + curWidth, y);

    // Invalidates the View to update the display.
    ViewCompat.postInvalidateOnAnimation(this);
}

縮放

在Detecting Common Gestures中,我們討論到GestureDetector可以幫助我們來檢測比如滑動、滾動、長按等手勢。而對於縮放,Android提供了ScaleGestureDetector. GestureDetector 以及 ScaleGestureDetector。

為了可以反饋檢測到的手勢事件,手勢探測器使用了監聽器對象ScaleGestureDetector.OnScaleGestureListener。如果你只關心部分手勢的話,Android還提供了ScaleGestureDetector.SimpleOnScaleGestureListener,你可以通過重寫它的方法來使用。

縮放基礎示例

下面的代碼是縮放所需要的基礎:

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

public MyCustomView(Context mContext){
    ...
    // View code goes here
    ...
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);
    return true;
}

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.scale(mScaleFactor, mScaleFactor);
    ...
    // onDraw() code goes here
    ...
    canvas.restore();
}

private class ScaleListener
        extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();

        // Don't let the object get too small or too large.
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

        invalidate();
        return true;
    }
}

稍微復雜點的示例

下面是一個稍微復雜一點的示例,它摘自與這節課所提供的示例InteractiveChart(PS:示例工程請參見原網頁)。InteractiveChart同時支持平移、縮放,它使用了ScaleGestureDetector的“平移”(getCurrentSpanX/Y)及“焦點” (getFocusX/Y)特性:

@Override
private RectF mCurrentViewport =
        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
private Rect mContentRect;
private ScaleGestureDetector mScaleGestureDetector;
...
public boolean onTouchEvent(MotionEvent event) {
    boolean retVal = mScaleGestureDetector.onTouchEvent(event);
    retVal = mGestureDetector.onTouchEvent(event) || retVal;
    return retVal || super.onTouchEvent(event);
}

/**
 * The scale listener, used for handling multi-finger scale gestures.
 */
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
        = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    /**
     * This is the active focal point in terms of the viewport. Could be a local
     * variable but kept here to minimize per-frame allocations.
     */
    private PointF viewportFocus = new PointF();
    private float lastSpanX;
    private float lastSpanY;

    // Detects that new pointers are going down.
    @Override
    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
        lastSpanX = ScaleGestureDetectorCompat.
                getCurrentSpanX(scaleGestureDetector);
        lastSpanY = ScaleGestureDetectorCompat.
                getCurrentSpanY(scaleGestureDetector);
        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {

        float spanX = ScaleGestureDetectorCompat.
                getCurrentSpanX(scaleGestureDetector);
        float spanY = ScaleGestureDetectorCompat.
                getCurrentSpanY(scaleGestureDetector);

        float newWidth = lastSpanX / spanX * mCurrentViewport.width();
        float newHeight = lastSpanY / spanY * mCurrentViewport.height();

        float focusX = scaleGestureDetector.getFocusX();
        float focusY = scaleGestureDetector.getFocusY();
        // Makes sure that the chart point is within the chart region.
        // See the sample for the implementation of hitTest().
        hitTest(scaleGestureDetector.getFocusX(),
                scaleGestureDetector.getFocusY(),
                viewportFocus);

        mCurrentViewport.set(
                viewportFocus.x
                        - newWidth * (focusX - mContentRect.left)
                        / mContentRect.width(),
                viewportFocus.y
                        - newHeight * (mContentRect.bottom - focusY)
                        / mContentRect.height(),
                0,
                0);
        mCurrentViewport.right = mCurrentViewport.left + newWidth;
        mCurrentViewport.bottom = mCurrentViewport.top + newHeight;
        ...
        // Invalidates the View to update the display.
        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);

        lastSpanX = spanX;
        lastSpanY = spanY;
        return true;
    }
};
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved