Android官方开发文档Training系列课程中文版:手势处理之拖拽或缩放
原文地址:https://developer.android.com/training/gestures/scale.html
這節(jié)課主要學(xué)習(xí)如何使用觸摸手勢(shì)來(lái)拖動(dòng)、放大屏幕上的對(duì)象。
拖動(dòng)對(duì)象
如果你的重點(diǎn)在Android 3.0以上的版本,那么你可以使用內(nèi)置的拖拽事件監(jiān)聽(tīng)器View.OnDragListener。
觸摸手勢(shì)最常見(jiàn)的操作就是使用它來(lái)拖動(dòng)屏幕上的對(duì)象。下面的代碼允許用戶拖動(dòng)屏幕上的圖像。要注意以下幾點(diǎn):
- 在拖動(dòng)操作中,APP會(huì)一直保持手指拖動(dòng)的軌跡,就算是另一只手指觸到屏幕也是。舉個(gè)例子,想象一根手指在拖動(dòng)著一張圖像,這時(shí)用戶將第二根手指放置到屏幕上,如果APP只是追蹤單根手指的軌跡,那么它會(huì)將第二根手指作為默認(rèn)位置,并會(huì)將圖像移動(dòng)到這個(gè)位置。
- 為了防止這樣的事件發(fā)生,APP需要區(qū)分第一根手指與其它手指。為此,需要追蹤 ACTION_POINTER_DOWN 及 ACTION_POINTER_UP 。ACTION_POINTER_DOWN 及 ACTION_POINTER_UP在第二根手指落下或抬起的時(shí)候由onTouchEvent()方法傳回。
- 在ACTION_POINTER_UP的情況下,示例提取了這個(gè)事件的索引,并確保當(dāng)前活動(dòng)的指針不是那個(gè)已經(jīng)不在屏幕上的指針。如果是那個(gè)指針的話,那么APP會(huì)選擇一個(gè)不同的指針使其活動(dòng)并保存它的X及Y的位置。一旦這個(gè)值被保存下來(lái),那么APP將會(huì)使用正確指針的數(shù)據(jù)一直計(jì)算剩余移動(dòng)的距離。
下面的代碼允許用戶在屏幕上拖動(dòng)對(duì)象。它記錄了當(dāng)前活動(dòng)指針的初始位置,計(jì)算了它所位移的距離,并將對(duì)象移動(dòng)到新的位置上。
這里要注意,代碼段使用了getActionMasked()方法。你應(yīng)該一直使用這個(gè)方法來(lái)接收MotionEvent對(duì)象的活動(dòng)。與getAction()方法不同,getActionMasked()工作于多點(diǎn)觸控模式下。它會(huì)返回被執(zhí)行的掩飾活動(dòng),不包括指針的索引比特。
// 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 positionfinal int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex);final float y = MotionEventCompat.getY(ev, pointerIndex);// Calculate the distance movedfinal float dx = x - mLastTouchX;final float dy = y - mLastTouchY;mPosX += dx;mPosY += dy;invalidate();// Remember this touch position for the next move eventmLastTouchX = 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; }平移
上面的部分展示了如何在屏幕上拖動(dòng)對(duì)象。另一個(gè)通用的場(chǎng)景就是平移了,平移的意思是:用戶的拖動(dòng)動(dòng)作引起的x及y軸方向上的滾動(dòng)。上面的代碼直接將MotionEvent攔截實(shí)現(xiàn)拖動(dòng)。這部分的代碼將會(huì)采用另一種更具有優(yōu)勢(shì)的方法,以便支持通用手勢(shì)。它重寫了GestureDetector.SimpleOnGestureListener的onScroll()方法。
只有用戶在使用手指移動(dòng)內(nèi)容時(shí),onScroll()才會(huì)被調(diào)用。onScroll()只有在手指按下的時(shí)候才會(huì)調(diào)用,一旦手指離開(kāi)屏幕,那么平移手勢(shì)也隨之終止。
下面是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; }下面是setViewportBottomLeft()方法的實(shí)現(xiàn),它主要實(shí)現(xiàn)了移動(dòng)內(nèi)容的邏輯:
/*** 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可以幫助我們來(lái)檢測(cè)比如滑動(dòng)、滾動(dòng)、長(zhǎng)按等手勢(shì)。而對(duì)于縮放,Android提供了ScaleGestureDetector. GestureDetector 以及 ScaleGestureDetector。
為了可以反饋檢測(cè)到的手勢(shì)事件,手勢(shì)探測(cè)器使用了監(jiān)聽(tīng)器對(duì)象ScaleGestureDetector.OnScaleGestureListener。如果你只關(guān)心部分手勢(shì)的話,Android還提供了ScaleGestureDetector.SimpleOnScaleGestureListener,你可以通過(guò)重寫它的方法來(lái)使用。
縮放基礎(chǔ)示例
下面的代碼是縮放所需要的基礎(chǔ):
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 ScaleListenerextends ScaleGestureDetector.SimpleOnScaleGestureListener {@Overridepublic 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;} }稍微復(fù)雜點(diǎn)的示例
下面是一個(gè)稍微復(fù)雜一點(diǎn)的示例,它摘自與這節(jié)課所提供的示例InteractiveChart(PS:示例工程請(qǐng)參見(jiàn)原網(wǎng)頁(yè))。InteractiveChart同時(shí)支持平移、縮放,它使用了ScaleGestureDetector的“平移”(getCurrentSpanX/Y)及“焦點(diǎn)” (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.@Overridepublic boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {lastSpanX = ScaleGestureDetectorCompat.getCurrentSpanX(scaleGestureDetector);lastSpanY = ScaleGestureDetectorCompat.getCurrentSpanY(scaleGestureDetector);return true;}@Overridepublic 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;} };總結(jié)
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:手势处理之拖拽或缩放的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux的基本使用
- 下一篇: Android官方开发文档Trainin