本课介绍如何使用触摸手势拖放和缩放屏幕上的对象,onTouchEvent()以便截取触摸事件。
请参阅以下相关资源:
拖动一个对象
如果您的目标是Android 3.0或更高版本,则可以使用内置的拖放事件侦听器View.OnDragListener,如拖放所述 。
触摸手势的常见操作是使用它在屏幕上拖动对象。以下片段让用户拖动屏幕上的图像。请注意以下几点:
- 在拖动(或滚动)操作中,即使手指放在屏幕上,应用也必须跟踪原始指针(手指)。例如,假设在拖动图像时,用户将第二根手指放在触摸屏上并抬起第一根手指。如果您的应用只是跟踪单个指针,它会将第二个指针视为默认值,并将图像移动到该位置。
- 为了防止这种情况发生,您的应用程序需要区分原始指针和任何后续指针。为此,它会跟踪处理多点触控手势中描述的 事件ACTION_POINTER_DOWN和 ACTION_POINTER_UP事件 。 并且 每当辅助指针关闭或向上时传递给回调函数。ACTION_POINTER_DOWNACTION_POINTER_UPonTouchEvent()
- 在这种ACTION_POINTER_UP情况下,该示例提取该索引并确保活动指针ID不是指不再触摸屏幕的指针。如果是,应用程序会选择一个不同的指针来激活并保存当前的X和Y位置。由于此保存的位置用于ACTION_MOVE 计算移动屏幕对象的距离,因此应用程序将始终使用正确的指针计算移动的距离。
以下片段使用户能够在屏幕上拖动一个对象。它记录活动指针的初始位置,计算指针行进的距离,并将对象移动到新的位置。正确地管理额外指针的可能性,如上所述。
请注意,片段使用该getActionMasked()方法。您应始终使用此方法(或更好的兼容版本 MotionEventCompat.getActionMasked())来检索a的操作 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 执行拖动的操作。本节中的片段利用了平台对常见手势的内置支持。它超越 onScroll()了 GestureDetector.SimpleOnGestureListener。
为了提供更多的上下文,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;
}
实行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);
}
使用触摸来执行缩放
正如检测常见手势中所述, GestureDetector可帮助您检测Android使用的常见手势,例如滚动,投掷和长按。为了扩展,Android提供了ScaleGestureDetector。GestureDetector并且ScaleGestureDetector可以在想要视图识别其他手势时一起使用。
为了报告检测到的手势事件,手势检测器使用传递给其构造函数的侦听器对象。ScaleGestureDetector用途 ScaleGestureDetector.OnScaleGestureListener。ScaleGestureDetector.SimpleOnScaleGestureListener 如果你不关心所有报告的事件,Android提供 了一个辅助类,你可以扩展它。
基本的缩放例子
这里是一个片段,说明了缩放所涉及的基本成分。
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这个类提供的示例中更复杂的示例。该InteractiveChart示例使用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;
}
};
Lastest Update:2018.04.25
联系我
QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ
公众号推荐: