W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
編寫(xiě):Andrwyw - 原文:http://developer.android.com/training/gestures/scale.html
本節(jié)課程講述,使用onTouchEvent()截獲觸摸事件后,如何使用觸摸手勢(shì)拖拽、縮放屏幕上的對(duì)象。
如果我們的目標(biāo)版本為3.0或以上,我們可以使用View.OnDragListener監(jiān)聽(tīng)內(nèi)置的拖放(drag-and-drop)事件,拖拽與釋放中有更多相關(guān)描述。
對(duì)于觸摸手勢(shì)來(lái)說(shuō),一個(gè)很常見(jiàn)的操作是在屏幕上拖拽一個(gè)對(duì)象。接下來(lái)的代碼段讓用戶(hù)可以拖拽屏幕上的圖片。需要注意以下幾點(diǎn):
onTouchEvent()
回調(diào)函數(shù)。下面的代碼段允許用戶(hù)拖拽屏幕上的對(duì)象。它會(huì)記錄操作中的點(diǎn)(active pointer)的初始位置,計(jì)算觸摸點(diǎn)移動(dòng)過(guò)的距離,再把對(duì)象移動(dòng)到新的位置。如上所述,它也正確地處理了額外觸摸點(diǎn)的可能。
需要注意的是,代碼段中使用了getActionMasked()函數(shù)。我們應(yīng)該始終使用這個(gè)函數(shù)(或者最好用MotionEventCompat.getActionMasked()這個(gè)兼容版本)來(lái)獲得MotionEvent對(duì)應(yīng)的動(dòng)作(action)。不像舊的getAction()函數(shù),getActionMasked()
就是設(shè)計(jì)用來(lái)處理多點(diǎn)觸摸的。它會(huì)返回執(zhí)行過(guò)的動(dòng)作的掩碼值,不包括該點(diǎn)的索引位。
// The ‘a(chǎn)ctive 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;
}
前一節(jié)展示了一個(gè),在屏幕上拖拽對(duì)象的例子。另一個(gè)常見(jiàn)的場(chǎng)景是平移(panning),平移是指用戶(hù)通過(guò)拖拽移動(dòng)引起x、y軸方向發(fā)生滾動(dòng)(scrolling)。上面的代碼段直接截獲了MotionEvent動(dòng)作來(lái)實(shí)現(xiàn)拖拽。這一部分的代碼段,利用了平臺(tái)對(duì)常用手勢(shì)的內(nèi)置支持。它重寫(xiě)了GestureDetector.SimpleOnGestureListener的onScroll()函數(shù)。
更詳細(xì)地說(shuō),當(dāng)用戶(hù)拖拽手指來(lái)平移內(nèi)容時(shí),onScroll()
函數(shù)就會(huì)被調(diào)用。onScroll()
函數(shù)只會(huì)在手指按下的情況下被調(diào)用,一旦手指離開(kāi)屏幕了,要么手勢(shì)終止,要么快速滑動(dòng)(fling)手勢(shì)開(kāi)始(如果手指在離開(kāi)屏幕前快速移動(dòng)了一段距離)。關(guān)于滾動(dòng)與快速滑動(dòng)的更多討論,可以查看滾動(dòng)手勢(shì)動(dòng)畫(huà)章節(jié)。
這里是onScroll()
的相關(guān)代碼段:
// 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()
函數(shù)中滑動(dòng)視窗(viewport)來(lái)響應(yīng)觸摸手勢(shì)的實(shí)現(xiàn):
/**
* 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);
}
如同檢測(cè)常用手勢(shì)章節(jié)中提到的,GestureDetector可以幫助我們檢測(cè)Android中的常見(jiàn)手勢(shì),例如滾動(dòng),快速滾動(dòng)以及長(zhǎng)按。對(duì)于縮放,Android也提供了ScaleGestureDetector類(lèi)。當(dāng)我們想讓view能識(shí)別額外的手勢(shì)時(shí),我們可以同時(shí)使用GestureDetector和ScaleGestureDetector類(lèi)。
為了報(bào)告檢測(cè)到的手勢(shì)事件,手勢(shì)檢測(cè)需要一個(gè)作為構(gòu)造函數(shù)參數(shù)的listener對(duì)象。ScaleGestureDetector使用ScaleGestureDetector.OnScaleGestureListener。Android提供了ScaleGestureDetector.SimpleOnScaleGestureListener類(lèi)作為幫助類(lèi),如果我們不是關(guān)注所有的手勢(shì)事件,我們可以繼承(extend)它。
下面的代碼段展示了縮放功能中的基本部分。
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;
}
}
這是本章節(jié)提供的InteractiveChart
示例中一個(gè)更復(fù)雜的示范。通過(guò)使用ScaleGestureDetector中的"span"(getCurrentSpanX/Y)和"focus"(getFocusX/Y)功能,InteractiveChart
示例同時(shí)支持滾動(dòng)(平移)以及多指縮放。
@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;
}
};
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: