手势识别官方教程(6)识别拖拽手势用GestureDetector.SimpleOnGestureListener和onTouchEvent
三种现实drag方式
1,在3.0以后可以直接用 View.OnDragListener (在onTouchEvent中调用某个view的startDrag())
2,onTouchEvent()
3,GestureDetector.SimpleOnGestureListener 的onScroll
Dragging
This lesson describes how to use touch gestures to drag, using onTouchEvent()
to intercept touch events.
1.Drag an Object
If you are targeting Android 3.0 or higher, you can use the built-in drag-and-drop event listeners with View.OnDragListener
, as described in Drag and Drop.
A common operation for a touch gesture is to use it to drag an object across the screen. The following snippet lets the user drag an on-screen image. Note the following:
- In a drag (or scroll) operation, the app has to keep track of the original pointer (finger), even if additional fingers get placed on the screen. For example, imagine that while dragging the image around, the user places a second finger on the touch screen and lifts the first finger. If your app is just tracking individual pointers, it will regard the second pointer as the default and move the image to that location.
拖拽和滚动手势时,要支持手指交换移动或拖拽,以拖图片为例,当第二个手指按图片且第一个手指离开后,第二个手指应仍然可以拖拽。
- To prevent this from happening, your app needs to distinguish between the original pointer and any follow-on pointers. To do this, it tracks the
ACTION_POINTER_DOWN
andACTION_POINTER_UP
events described in Handling Multi-Touch Gestures.ACTION_POINTER_DOWN
andACTION_POINTER_UP
are passed to theonTouchEvent()
callback whenever a secondary pointer goes down or up.为了实现第一条,应该注意原始手指和后续手指。
- In the
ACTION_POINTER_UP
case, the example extracts this index and ensures that the active pointer ID is not referring to a pointer that is no longer touching the screen. If it is, the app selects a different pointer to be active and saves its current X and Y position. Since this saved position is used in theACTION_MOVE
case to calculate the distance to move the onscreen object, the app will always calculate the distance to move using data from the correct pointer.
The following snippet enables a user to drag an object around on the screen. It records the initial position of the active pointer, calculates the distance the pointer traveled, and moves the object to the new position. It correctly manages the possibility of additional pointers, as described above.
Notice that the snippet uses the getActionMasked()
method. You should always use this method (or better yet, the compatability version MotionEventCompat.getActionMasked()
) to retrieve the action of a MotionEvent
. Unlike the older getAction()
method, getActionMasked()
is designed to work with multiple pointers. It returns the masked action being performed, without including the pointer index bits.
用 getActionMasked() 而不用 getAction(),前者支持多点触摸。
在屏幕上拖拽一个目标的示例
1 // The ‘active pointer’ is the one currently moving our object. 2 private int mActivePointerId = INVALID_POINTER_ID; 3 4 @Override 5 public boolean onTouchEvent(MotionEvent ev) { 6 // Let the ScaleGestureDetector inspect all events. 7 mScaleDetector.onTouchEvent(ev); 8 9 final int action = MotionEventCompat.getActionMasked(ev); 10 11 switch (action) { 12 case MotionEvent.ACTION_DOWN: { 13 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 14 final float x = MotionEventCompat.getX(ev, pointerIndex); 15 final float y = MotionEventCompat.getY(ev, pointerIndex); 16 17 // Remember where we started (for dragging) 18 mLastTouchX = x; 19 mLastTouchY = y; 20 // Save the ID of this pointer (for dragging) 21 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 22 break; 23 } 24 25 case MotionEvent.ACTION_MOVE: { 26 // Find the index of the active pointer and fetch its position 27 final int pointerIndex = 28 MotionEventCompat.findPointerIndex(ev, mActivePointerId); 29 30 final float x = MotionEventCompat.getX(ev, pointerIndex); 31 final float y = MotionEventCompat.getY(ev, pointerIndex); 32 33 // Calculate the distance moved 34 final float dx = x - mLastTouchX; 35 final float dy = y - mLastTouchY; 36 37 mPosX += dx; 38 mPosY += dy; 39 40 invalidate(); 41 42 // Remember this touch position for the next move event 43 mLastTouchX = x; 44 mLastTouchY = y; 45 46 break; 47 } 48 49 case MotionEvent.ACTION_UP: { 50 mActivePointerId = INVALID_POINTER_ID; 51 break; 52 } 53 54 case MotionEvent.ACTION_CANCEL: { 55 mActivePointerId = INVALID_POINTER_ID; 56 break; 57 } 58 59 case MotionEvent.ACTION_POINTER_UP: { 60 61 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 62 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 63 64 if (pointerId == mActivePointerId) { 65 // This was our active pointer going up. Choose a new 66 // active pointer and adjust accordingly. 67 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 68 mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex); 69 mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex); 70 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 71 } 72 break; 73 } 74 } 75 return true; 76 }
2.Drag to Pan(拖到托盘上)
The previous section showed an example of dragging an object around the screen. Another common scenario is panning, which is when a user's dragging motion causes scrolling in both the x and y axes. The above snippet directly intercepted the MotionEvent
actions to implement dragging. The snippet in this section takes advantage of the platform's built-in support for common gestures. It overrides onScroll()
in GestureDetector.SimpleOnGestureListener
.
To provide a little more context, onScroll()
is called when a user is dragging his finger to pan the content. onScroll()
is only called when a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, or a fling gesture is started (if the finger was moving with some speed just before it was lifted). For more discussion of scrolling vs. flinging, see Animating a Scroll Gesture.
Here is the snippet for onScroll()
:
1 // The current viewport. This rectangle represents the currently visible 2 // chart domain and range. 3 private RectF mCurrentViewport = 4 new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); 5 6 // The current destination rectangle (in pixel coordinates) into which the 7 // chart data should be drawn. 8 private Rect mContentRect; 9 10 private final GestureDetector.SimpleOnGestureListener mGestureListener 11 = new GestureDetector.SimpleOnGestureListener() { 12 ... 13 14 @Override 15 public boolean onScroll(MotionEvent e1, MotionEvent e2, 16 float distanceX, float distanceY) { 17 // Scrolling uses math based on the viewport (as opposed to math using pixels). 18 19 // Pixel offset is the offset in screen pixels, while viewport offset is the 20 // offset within the current viewport. 21 float viewportOffsetX = distanceX * mCurrentViewport.width() 22 / mContentRect.width(); 23 float viewportOffsetY = -distanceY * mCurrentViewport.height() 24 / mContentRect.height(); 25 ... 26 // Updates the viewport, refreshes the display. 27 setViewportBottomLeft( 28 mCurrentViewport.left + viewportOffsetX, 29 mCurrentViewport.bottom + viewportOffsetY); 30 ... 31 return true; 32 }
The implementation of onScroll()
scrolls the viewport in response to the touch gesture:
1 /** 2 * Sets the current viewport (defined by mCurrentViewport) to the given 3 * X and Y positions. Note that the Y value represents the topmost pixel position, 4 * and thus the bottom of the mCurrentViewport rectangle. 5 */ 6 private void setViewportBottomLeft(float x, float y) { 7 /* 8 * Constrains within the scroll range. The scroll range is simply the viewport 9 * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the 10 * extremes were 0 and 10, and the viewport size was 2, the scroll range would 11 * be 0 to 8. 12 */ 13 14 float curWidth = mCurrentViewport.width(); 15 float curHeight = mCurrentViewport.height(); 16 x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth)); 17 y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX)); 18 19 mCurrentViewport.set(x, y - curHeight, x + curWidth, y); 20 21 // Invalidates the View to update the display. 22 ViewCompat.postInvalidateOnAnimation(this); 23 }