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

來源:互聯網
上載者:User

標籤:

原文地址: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;@Overridepublic 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() {...@Overridepublic 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());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {    // Let the ScaleGestureDetector inspect all events.    mScaleDetector.onTouchEvent(ev);    return true;}@Overridepublic 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)特性:

@Overrideprivate 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;    }};

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.