android 拖动和缩放 [Dragging and Scaling]

Dragging and Scaling [拖动和缩放]

这节课描述如何使用触摸手势来拖动和缩放屏幕上的对象,使用 onTouchEvent()方法来拦截触摸事件。

Drag an Object [拖拽一个对象]

如果你的目标系统版本是Android 3.0 或更高,你就能使用内置拖放事件 监听器View.OnDragListener ,在Drag and Drop中描述。

触屏手势的一个常用的操作是在屏幕上拖动一个对象,下面的摘要使用户拖动一个屏幕上的图像。请注意以下:

1.在一个拖动(或滚动)操作中,应用程序需要保持对最初始点(手指触摸)的跟踪,即使有其他的手指放置在屏幕上。例如,想象一下,当用户在屏幕上拖动图像,用户将第二个手指放在了屏幕上,并将第一个手指抬起。如果你的应用程序仅仅追踪单个点,那么它将会把第二个点作为目标位置并将图像移向那个位置。

2.为了阻止这种情况的发生,你的应用程序需要区分初始的点和后续的点。为此,需要追踪在Handling Multi-Touch Gestures描述的 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP事件当后续的点按下或抬起时,ACTION_POINTER_DOWN和ACTION_POINTER_UP将会被传递到onTouchEvent()回调函数。

3.在ACTION_POINTER_UP事件的情况下,示例提取该触摸点的index,并确定该活动点ID没有表示一个不再触摸屏幕的点。如果是,应用程序会选取一个不同的点进行激活,并保存它的x和y坐标位置。由于这个保存的位置被用到ACTION_MOVE情况下来计算对象移动的距离,所以应用程序将使用正确的点的数据计算移动的距离和位置。

下面的代码段能使用户在屏幕上拖动一个对象。它记录了点的初始位置,计算点移动的距离,并将对象移动到新的位置。它正确的处理了额外点的可能性。

注意的是这段代码使用了getActionMasked()方法。你应该总是使用这个方法(使用兼容的版本 MotionEventCompat.getActionMasked()更好)去获取一个MotionEvent的action。不像旧版本的 getAction()方法getActionMasked() 被设计用来处理多点触控,当执行时,它只返回action而不包括点的索引值(index).
// 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;
}

Drag to Pan [因拖动而平移]


前一节显示了在屏幕上拖动一个对象的例子。另一种常见的情形是平移,这是当用户的拖动动作导致对象在x轴和y轴方向的滚动。上面的代码段直接截获MotionEvent的动作来实现拖动。本节中的代码段,充分利用平台内置支持的常用手势。它在GestureDetector.SimpleOnGestureListener里覆写onscroll()方法。

为了提供更多的内容,当用户拖动手指去移动对象时调用onscroll()方法。只有在手指按下时onscroll()才会被调用,一旦手指离开屏幕或者一个Fling手势动作开始(如果手指是在移动的时候离开屏幕的),这个手势动作就结束了。关于scrooling 和 fiinging更多的讨论,请参阅 Animating a Scroll Gesture .


这里是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;
}

onScroll()  的实现方法 响应触摸手势而滚动视图窗口:
/**
 * 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);
}

Use Touch to Perform Scaling [使用触摸执行缩放]

就像 Detecting Common Gestures里探讨的,GestureDetector能够帮助你监听Android常用的手势 ,例如 scrolling、flinging和long press。对于缩放,Android提供了ScaleGestureDetector. GestureDetector和ScaleGestureDetector能被用来检测更多的手势。

为了通知检测到的手势事件,手势探测器使用从它们的构造函数传递进来的 监听器对象。ScaleGestureDetector 使用ScaleGestureDetector.OnScaleGestureListener. Android 提供ScaleGestureDetector.SimpleOnScaleGestureListener 作为一个辅助类,如果你不关心所有的通知事件,你可以继承扩展它.

Basic scaling example [基础的缩放示例]

下面的代码段举例说明  缩放涉及的基本调用
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;
    }
}

More complex scaling example [较复杂的缩放示例]

下面是 一个较复杂的示例,来自本类提供的   InteractiveChart示例项目, InteractiveChart 示例支持 多个手指的scrolling(panning)和scaling事件,通过使用ScaleGestureDetector "span" (getCurrentSpanX/Y) 和 "focus" (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;
    }
};

官方文档: http://developer.android.com/training/gestures/scale.html#scale
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值