普通手势

参考地址:http://developer.android.com/training/gestures/detector.html#data
当用户一根或多根手指在屏幕上运动的时候,就开始产生了手势事件,我们用onTouchEvent()回调方法来处理。

为Activity 或View捕捉触摸事件

使用getActionMasked()来提取event中的action。

public class MainActivity extends Activity {...// This example shows an Activity, but you would use the same approach if// you were subclassing a View.@Overridepublic boolean onTouchEvent(MotionEvent event){     int action = MotionEventCompat.getActionMasked(event);    switch(action) {        case (MotionEvent.ACTION_DOWN) :            Log.d(DEBUG_TAG,"Action was DOWN");            return true;        case (MotionEvent.ACTION_MOVE) :            Log.d(DEBUG_TAG,"Action was MOVE");            return true;        case (MotionEvent.ACTION_UP) :            Log.d(DEBUG_TAG,"Action was UP");            return true;        case (MotionEvent.ACTION_CANCEL) :            Log.d(DEBUG_TAG,"Action was CANCEL");            return true;        case (MotionEvent.ACTION_OUTSIDE) :            Log.d(DEBUG_TAG,"Movement occurred outside bounds " +                    "of current screen element");            return true;              default :             return super.onTouchEvent(event);    }      }

为一个View添加触摸事件

你可以使用View的View.OnTouchListener监听器为任意的View注册监听事件,而不需要继承View重写onTouchEvent()。

View myView = findViewById(R.id.my_view); myView.setOnTouchListener(new OnTouchListener() {    public boolean onTouch(View v, MotionEvent event) {        // ... Respond to touch events               return true;    }});

注意,如果在ACTION_DOWN事件中return false,那么接下来的ACTION_MOVE 和 ACTION_UP事件都不会被回调

监测所有支持的手势

public class MainActivity extends Activity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener{    private static final String DEBUG_TAG = "Gestures";    private GestureDetectorCompat mDetector;     // Called when the activity is first created.     @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // Instantiate the gesture detector with the        // application context and an implementation of        // GestureDetector.OnGestureListener        mDetector = new GestureDetectorCompat(this,this);        // Set the gesture detector as the double tap        // listener.        mDetector.setOnDoubleTapListener(this);    }    @Override     public boolean onTouchEvent(MotionEvent event){         this.mDetector.onTouchEvent(event);        // Be sure to call the superclass implementation        return super.onTouchEvent(event);    }    @Override    public boolean onDown(MotionEvent event) {         Log.d(DEBUG_TAG,"onDown: " + event.toString());         return true;    }    @Override    public boolean onFling(MotionEvent event1, MotionEvent event2,             float velocityX, float velocityY) {        Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());        return true;    }    @Override    public void onLongPress(MotionEvent event) {        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());     }    @Override    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,            float distanceY) {        Log.d(DEBUG_TAG, "onScroll: " + e1.toString()+e2.toString());        return true;    }    @Override    public void onShowPress(MotionEvent event) {        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());    }    @Override    public boolean onSingleTapUp(MotionEvent event) {        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());        return true;    }    @Override    public boolean onDoubleTap(MotionEvent event) {        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());        return true;    }    @Override    public boolean onDoubleTapEvent(MotionEvent event) {        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());        return true;    }    @Override    public boolean onSingleTapConfirmed(MotionEvent event) {        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());        return true;    }}

监测支持手势的子集

如果你只想处理一些简单手势的,那么可以继承GestureDetector.SimpleOnGestureListener而不用实现GestureDetector.OnGestureListener监听。
同样,在onDown() 中return false,那么接下来的所有事件都不会发生了。

public class MainActivity extends Activity {     private GestureDetectorCompat mDetector;     @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mDetector = new GestureDetectorCompat(this, new MyGestureListener());    }    @Override     public boolean onTouchEvent(MotionEvent event){         this.mDetector.onTouchEvent(event);        return super.onTouchEvent(event);    }    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {        private static final String DEBUG_TAG = "Gestures";         @Override        public boolean onDown(MotionEvent event) {             Log.d(DEBUG_TAG,"onDown: " + event.toString());             return true;        }        @Override        public boolean onFling(MotionEvent event1, MotionEvent event2,                 float velocityX, float velocityY) {            Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());            return true;        }    }}

跟踪运动速度

原文地址:http://developer.android.com/training/gestures/movement.html

有一些不同的方法来跟踪一个手势的运动,根据不同程序的需要:

  • 移动的开始和结束位置(例如,将一个屏幕上的物体从a点移动到B点)。
  • 根据x、y坐标决定移动的方向
  • 历史点。通过getHistorySize()的方法得到手势历史的大小。getHistorical方法可以获得每个点的位置、大小、时间和压力
  • 点的运动速速

跟踪速度

Android提供 VelocityTracker类和 Support Library中提供的VelocityTrackerCompat类来帮助我们跟踪触摸事件的速度。

public class MainActivity extends Activity {    private static final String DEBUG_TAG = "Velocity";        ...    private VelocityTracker mVelocityTracker = null;    @Override    public boolean onTouchEvent(MotionEvent event) {        int index = event.getActionIndex();        int action = event.getActionMasked();        int pointerId = event.getPointerId(index);        switch(action) {            case MotionEvent.ACTION_DOWN:                if(mVelocityTracker == null) {                    // Retrieve a new VelocityTracker object to watch the velocity of a motion.                    mVelocityTracker = VelocityTracker.obtain();                }                else {                    // Reset the velocity tracker back to its initial state.                    mVelocityTracker.clear();                }                // Add a user's movement to the tracker.                mVelocityTracker.addMovement(event);                break;            case MotionEvent.ACTION_MOVE:                mVelocityTracker.addMovement(event);                // When you want to determine the velocity, call                 // computeCurrentVelocity(). Then call getXVelocity()                 // and getYVelocity() to retrieve the velocity for each pointer ID.                 mVelocityTracker.computeCurrentVelocity(1000);                // Log velocity of pixels per second                // Best practice to use VelocityTrackerCompat where possible.                Log.d("", "X velocity: " +                         VelocityTrackerCompat.getXVelocity(mVelocityTracker,                         pointerId));                Log.d("", "Y velocity: " +                         VelocityTrackerCompat.getYVelocity(mVelocityTracker,                        pointerId));                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                // Return a VelocityTracker object back to be re-used by others.                mVelocityTracker.recycle();                break;        }        return true;    }}

注意:请注意,你应该在ACTION_MOVE事件后计算速度,而不是ACTION_UP之后。因为ACTION_UP后,X和Y速度为0。

滚动手势

参考地址:http://developer.android.com/training/gestures/scroll.html
本文介绍使用scrollers响应触摸手势来显示一个滚动效果。
ScrollerOverScroller很类似,都可以针对Touch Event产生一个滚动动画。但是OverScroller包含了一些方法可以告诉用户他们已经到了内容边缘,InteractiveChart 例子中的EdgeEffectCompat类就展示了一个绚丽的用户到达内容边缘的效果。

注意:建议使用OverScroller而不是Scroller类来进行滚动动画。OverScroller有很好的向后兼容性。还要注意,一般你只在实现滚动自己的情况下,只需要实现scrollers。而ScrollViewHorizontalScrollView对它们内嵌的布局中已经做了所有的事情。

理解滚动术语

滚动在X和Y两个方向进行时,称为“panning”。滚动有两种类型,拖拽(Draging)和快速滑动(Flinging)。

  • Dragging :简单的dragging需要实现GestureDetector.OnGestureListener中的onScroll()方法。
  • Flinging :Flinging是用户拖拽一个view并进行快速的上下滑动。需要实现GestureDetector.OnGestureListener中的onFling()方法,并使用scroller 对象。这就是本文的主题。

实现基于触摸的滚动

InteractiveChart 例子,显示了一个可以缩放、左右滚动的图表。它里面实现了GestureDetector.SimpleOnGestureListener 中的onFling()方法,并使用了OverScroller来跟踪快速滑动手势。当用户滑到了内容边缘,则产生一个绚丽的效果。下面时onFling方法的代码片段:

// The current viewport. This rectangle represents the currently visible // chart domain and range. The viewport is the part of the app that the// user manipulates via touch gestures.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 OverScroller mScroller;private RectF mScrollerStartViewport;...private final GestureDetector.SimpleOnGestureListener mGestureListener        = new GestureDetector.SimpleOnGestureListener() {    @Override    public boolean onDown(MotionEvent e) {        // Initiates the decay phase of any active edge effects.        releaseEdgeEffects();        mScrollerStartViewport.set(mCurrentViewport);        // Aborts any active scroll animations and invalidates.        mScroller.forceFinished(true);        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);        return true;    }    ...    @Override    public boolean onFling(MotionEvent e1, MotionEvent e2,             float velocityX, float velocityY) {        fling((int) -velocityX, (int) -velocityY);        return true;    }};private void fling(int velocityX, int velocityY) {    // Initiates the decay phase of any active edge effects.    releaseEdgeEffects();    // Flings use math in pixels (as opposed to math based on the viewport).    Point surfaceSize = computeScrollSurfaceSize();    mScrollerStartViewport.set(mCurrentViewport);    int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left -             AXIS_X_MIN) / (            AXIS_X_MAX - AXIS_X_MIN));    int startY = (int) (surfaceSize.y * (AXIS_Y_MAX -             mScrollerStartViewport.bottom) / (            AXIS_Y_MAX - AXIS_Y_MIN));    // Before flinging, aborts the current animation.    mScroller.forceFinished(true);    // Begins the animation    mScroller.fling(            // Current scroll position            startX,            startY,            velocityX,            velocityY,            /* * Minimum and maximum scroll positions. The minimum scroll * position is generally zero and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset should be 800 pixels. */            0, surfaceSize.x - mContentRect.width(),            0, surfaceSize.y - mContentRect.height(),            // The edges of the content. This comes into play when using            // the EdgeEffect class to draw "glow" overlays.            mContentRect.width() / 2,            mContentRect.height() / 2);    // Invalidates to trigger computeScroll()    ViewCompat.postInvalidateOnAnimation(this);}

当onFling()调用postInvalidateOnAnimation(),它触发computeScroll()来更新x和y的值,这通常是使用scroller 对象来对View进行滚动时发生的。
大多数View通过scroller对象的x和y坐标直接传给scrollTo(),下面computeScroll()的实现,采用了一个不同的方法,调用computeScrollOffset()方法来获取当前位置的x、y值。当达到了overscroll 的条件(放大的视图,并且x、y超出边界,并且还没有显示overscroll),代码中将产生一个 overscroll的绚丽效果,并调用postInvalidateOnAnimation()来刷新View:

// Edge effect / overscroll tracking objects.private EdgeEffectCompat mEdgeEffectTop;private EdgeEffectCompat mEdgeEffectBottom;private EdgeEffectCompat mEdgeEffectLeft;private EdgeEffectCompat mEdgeEffectRight;private boolean mEdgeEffectTopActive;private boolean mEdgeEffectBottomActive;private boolean mEdgeEffectLeftActive;private boolean mEdgeEffectRightActive;@Overridepublic void computeScroll() {    super.computeScroll();    boolean needsInvalidate = false;    // The scroller isn't finished, meaning a fling or programmatic pan     // operation is currently active.    if (mScroller.computeScrollOffset()) {        Point surfaceSize = computeScrollSurfaceSize();        int currX = mScroller.getCurrX();        int currY = mScroller.getCurrY();        boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN                || mCurrentViewport.right < AXIS_X_MAX);        boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN                || mCurrentViewport.bottom < AXIS_Y_MAX);        /* * If you are zoomed in and currX or currY is * outside of bounds and you're not already * showing overscroll, then render the overscroll * glow edge effect. */        if (canScrollX                && currX < 0                && mEdgeEffectLeft.isFinished()                && !mEdgeEffectLeftActive) {            mEdgeEffectLeft.onAbsorb((int)                     OverScrollerCompat.getCurrVelocity(mScroller));            mEdgeEffectLeftActive = true;            needsInvalidate = true;        } else if (canScrollX                && currX > (surfaceSize.x - mContentRect.width())                && mEdgeEffectRight.isFinished()                && !mEdgeEffectRightActive) {            mEdgeEffectRight.onAbsorb((int)                     OverScrollerCompat.getCurrVelocity(mScroller));            mEdgeEffectRightActive = true;            needsInvalidate = true;        }        if (canScrollY                && currY < 0                && mEdgeEffectTop.isFinished()                && !mEdgeEffectTopActive) {            mEdgeEffectTop.onAbsorb((int)                     OverScrollerCompat.getCurrVelocity(mScroller));            mEdgeEffectTopActive = true;            needsInvalidate = true;        } else if (canScrollY                && currY > (surfaceSize.y - mContentRect.height())                && mEdgeEffectBottom.isFinished()                && !mEdgeEffectBottomActive) {            mEdgeEffectBottom.onAbsorb((int)                     OverScrollerCompat.getCurrVelocity(mScroller));            mEdgeEffectBottomActive = true;            needsInvalidate = true;        }        ...    }

下面是执行缩放的代码:

// Custom object that is functionally similar to ScrollerZoomer mZoomer;private PointF mZoomFocalPoint = new PointF();...// If a zoom is in progress (either programmatically or via double// touch), performs the zoom.if (mZoomer.computeZoom()) {    float newWidth = (1f - mZoomer.getCurrZoom()) *             mScrollerStartViewport.width();    float newHeight = (1f - mZoomer.getCurrZoom()) *             mScrollerStartViewport.height();    float pointWithinViewportX = (mZoomFocalPoint.x -             mScrollerStartViewport.left)            / mScrollerStartViewport.width();    float pointWithinViewportY = (mZoomFocalPoint.y -             mScrollerStartViewport.top)            / mScrollerStartViewport.height();    mCurrentViewport.set(            mZoomFocalPoint.x - newWidth * pointWithinViewportX,            mZoomFocalPoint.y - newHeight * pointWithinViewportY,            mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),            mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));    constrainViewport();    needsInvalidate = true;}if (needsInvalidate) {    ViewCompat.postInvalidateOnAnimation(this);}

就是这个computeScrollSurfaceSize()方法,它计算了当前的滚动平面的像素尺寸。比如:如果整个图表都可见,那就返回mContentRect的当前尺寸。如果图表在两个方向都放大了200%,则返回的值在水平和竖直方向都返回2倍的大小。

private Point computeScrollSurfaceSize() {    return new Point(            (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)                    / mCurrentViewport.width()),            (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)                    / mCurrentViewport.height()));}

处理多点触摸手势

参考地址:http://developer.android.com/training/gestures/multi.html

跟踪多个手指

当多个手指(Pointer)同时触摸屏幕,系统生成下面的触摸事件:

  • ACTION_DOWN:当第一个手指触摸了屏幕,就触发了。在MotionEvent中它指向的手指index永远为0。
  • ACTION_POINTER_DOWN:除了第一个手指,另外的手指戳到屏幕触发此事件,通过调用getActionIndex()方法返回手指的index。
  • ACTION_MOVE
  • ACTION_POINTER_UP:当非第一个手指Up的时候调用。
  • ACTION_UP:当最后一根手指离开屏幕调用
    你要在MotionEvent中通过每一个手指的indexID来跟踪每一根手指。
  • Index:MotionEvent使用一个数组存储了每一根手指的信息。手指的index就是这个数组的position。大多数情况下你要使用MotionEvent 来和手指交互,都是取Index。
  • ID:每个手指也有一个ID,保证在整个触摸事件允许跟踪单个手指。
    index是会变得,但ID不会变,只要手指是活跃的。使用getPointerId() 来获得一个手指的ID。因此,对于连续的触摸事件,通过findPointerIndex() 方法传入一个固定的ID值,就可以返回这个手指的index。
private int mActivePointerId;public boolean onTouchEvent(MotionEvent event) {    ....    // Get the pointer ID    mActivePointerId = event.getPointerId(0);    // ... Many touch events later...    // Use the pointer ID to find the index of the active pointer     // and fetch its position    int pointerIndex = event.findPointerIndex(mActivePointerId);    // Get the pointer's current position    float x = event.getX(pointerIndex);    float y = event.getY(pointerIndex);}

获取事件的Action

你要使用getActionMasked()(也许更好的是兼容的Support Library中提供的MotionEventCompat.getActionMasked()方法)方法获取事件的action。不像老版的getAction()方法,getActionMasked()是为多点触摸设计的。你可以使用getActionIndex()方法返回这个action对应的手指index。

int action = MotionEventCompat.getActionMasked(event);// Get the index of the pointer associated with the action.int index = MotionEventCompat.getActionIndex(event);int xPos = -1;int yPos = -1;Log.d(DEBUG_TAG,"The action is " + actionToString(action));if (event.getPointerCount() > 1) {    Log.d(DEBUG_TAG,"Multitouch event");     // The coordinates of the current screen contact, relative to     // the responding View or Activity.     xPos = (int)MotionEventCompat.getX(event, index);    yPos = (int)MotionEventCompat.getY(event, index);} else {    // Single touch event    Log.d(DEBUG_TAG,"Single touch event");     xPos = (int)MotionEventCompat.getX(event, index);    yPos = (int)MotionEventCompat.getY(event, index);}...// Given an action int, returns a string descriptionpublic static String actionToString(int action) {    switch (action) {        case MotionEvent.ACTION_DOWN: return "Down";        case MotionEvent.ACTION_MOVE: return "Move";        case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";        case MotionEvent.ACTION_UP: return "Up";        case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";        case MotionEvent.ACTION_OUTSIDE: return "Outside";        case MotionEvent.ACTION_CANCEL: return "Cancel";    }    return "";}

拖拽和缩放

参考地址:http://developer.android.com/training/gestures/scale.html#pan

如果你的设备是Android 3.0 及以上版本,可以使用View.OnDragListener来处理拖拽事件。

拖拽一个对象

在拖拽事件中,即使加入了新的手指,app也不得不跟踪最开始的手指。例如,在屏幕上拖拽一个图片,然后放入第二根手指,并松开第一个手指。如果app单独处理单个手指的话,那么图片就会停在那里不动了。
为了防止这样的事发生,在ACTION_POINTER_UP事件中,提取了index并确保激活的手指id不再指向离开了屏幕的手指。这样,app选择了一个不同的手指来保存x、y坐标,因为这个坐标是ACTION_MOVE事件中计算移动对象的距离的,所以app一直都在使用正确的手指计算移动数据。

// The ‘active pointer’ is the one currently moving our object.private int mActivePointerId = INVALID_POINTER_ID;@Overridepublic 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

当用户用手指拖拽内容时,onScroll()只在手指按下时被调用,一旦手指离开屏幕,手势要么结束,要么onFling(离开前快速滑动)调用。

// 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() {...@Overridepublic 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;}/** * 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);}

使用触摸执行缩放

Android提供ScaleGestureDetector.OnScaleGestureListener来处理触摸的缩放。基本缩放的代码如下:

private ScaleGestureDetector mScaleDetector;private float mScaleFactor = 1.f;public MyCustomView(Context mContext){    ...    // View code goes here    ...    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());}@Overridepublic boolean onTouchEvent(MotionEvent ev) {    // Let the ScaleGestureDetector inspect all events.    mScaleDetector.onTouchEvent(ev);    return true;}@Overridepublic 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例子支持多手指的滚动和缩放:

@Overrideprivate 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;    }};

处理ViewGroup中的触摸事件

参考地址:http://developer.android.com/training/gestures/viewgroup.html
ViewGroup中,为确保每个子View能接收到触摸事件,覆盖onInterceptTouchEvent()方法。

当ViewGroup及其子View发生触摸事件都会调用onInterceptTouchEvent()方法。如果onInterceptTouchEvent()返回true,MotionEvent就会被拦截,意味着它不会传递到子View,而是执行父View的onTouchEvent()。
onInterceptTouchEvent()方法给了父View一个机会看它的子View执行之前的触摸事件。如果你onInterceptTouchEvent()返回true,那么你的子View准备处理触摸事件的现在会接收ACTION_CANCEL事件,并将手指的事件传递到父View的onTouchEvent()中;因为通常onInterceptTouchEvent()方法会返回false,并将事件通过View Hierarchy传递到子View,让每个子View执行它自己的onTouchEvent()
下面的代码中,MyViewGroup继承ViewGroup,MyViewGroup有多个子View。如果水平拖动一个子View,子View不再响应Touch events,这时MyViewGroup应该处理滚动这些内容的事件;然而,如果你按下一个按钮,或垂直滚动子View,MyViewGroup就不应该拦截这些事件,因为子View需要响应这些事件。在这些情况下,onInterceptTouchEvent()将返回false,MyViewGroup 的onTouchEvent()方法不会被调用。

public class MyViewGroup extends ViewGroup {    private int mTouchSlop;    ...    ViewConfiguration vc = ViewConfiguration.get(view.getContext());    mTouchSlop = vc.getScaledTouchSlop();    ...    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */        final int action = MotionEventCompat.getActionMasked(ev);        // Always handle the case of the touch gesture being complete.        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {            // Release the scroll.            mIsScrolling = false;            return false; // Do not intercept touch event, let the child handle it        }        switch (action) {            case MotionEvent.ACTION_MOVE: {                if (mIsScrolling) {                    // We're currently scrolling, so yes, intercept the                     // touch event!                    return true;                }                // If the user has dragged her finger horizontally more than                 // the touch slop, start the scroll                // left as an exercise for the reader                final int xDiff = calculateDistanceX(ev);                 // Touch slop should be calculated using ViewConfiguration                 // constants.                if (xDiff > mTouchSlop) {                     // Start scrolling!                    mIsScrolling = true;                    return true;                }                break;            }            ...        }        // In general, we don't want to intercept touch events. They should be         // handled by the child view.        return false;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        // Here we actually handle the touch event (e.g. if the action is ACTION_MOVE,         // scroll this container).        // This method will only be called if the touch event was intercepted in         // onInterceptTouchEvent        ...    }}

ViewGroup还提供了一个requestDisallowInterceptTouchEvent()方法,当它的一个子View不想它的父View或祖先View使用onInterceptTouchEvent()拦截它的touch event事件时调用。.

使用ViewConfiguration常量

在Android系统中使用 ViewConfiguration类可以访问通用的距离、速度和时间。
Touch slop典型的用处时为了防止用户在进行一些其他操作意外的滚动。ViewConfiguration中另外两个很常见的方法是getScaledMinimumFlingVelocity()getScaledMaximumFlingVelocity()。这2个方法返回最小和最大速度(像素每秒)初始化一个快速滑动:

ViewConfiguration vc = ViewConfiguration.get(view.getContext());private int mSlop = vc.getScaledTouchSlop();private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();...case MotionEvent.ACTION_MOVE: {    ...    float deltaX = motionEvent.getRawX() - mDownX;    if (Math.abs(deltaX) > mSlop) {        // A swipe occurred, do something    }...case MotionEvent.ACTION_UP: {    ...    } if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity            && velocityY < velocityX) {        // The criteria have been satisfied, do something    }}

扩展子View的可触区域

Android提供了TouchDelegate类让父View扩展子View的触摸区域,这个在子View非常小而又非要一个较大触摸区域时很有用。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parent_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" >     <ImageButton android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:src="@drawable/icon" /></RelativeLayout>

在RelativeLayout中扩展ImageButton的触摸区域:

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // Get the parent view        View parentView = findViewById(R.id.parent_layout);        parentView.post(new Runnable() {            // Post in the parent's message queue to make sure the parent            // lays out its children before you call getHitRect()            @Override            public void run() {                // The bounds for the delegate view (an ImageButton                // in this example)                Rect delegateArea = new Rect();                ImageButton myButton = (ImageButton) findViewById(R.id.button);                myButton.setEnabled(true);                myButton.setOnClickListener(new View.OnClickListener() {                    @Override                    public void onClick(View view) {                        Toast.makeText(MainActivity.this,                                 "Touch occurred within ImageButton touch region.",                                 Toast.LENGTH_SHORT).show();                    }                });                // The hit rectangle for the ImageButton                myButton.getHitRect(delegateArea);                // Extend the touch area of the ImageButton beyond its bounds                // on the right and bottom.                delegateArea.right += 100;                delegateArea.bottom += 100;                // Instantiate a TouchDelegate.                // "delegateArea" is the bounds in local coordinates of                 // the containing view to be mapped to the delegate view.                // "myButton" is the child view that should receive motion                // events.                TouchDelegate touchDelegate = new TouchDelegate(delegateArea,                         myButton);                // Sets the TouchDelegate on the parent view, such that touches                 // within the touch delegate bounds are routed to the child.                if (View.class.isInstance(myButton.getParent())) {                    ((View) myButton.getParent()).setTouchDelegate(touchDelegate);                }            }        });    }}

更多相关文章

  1. android中监听layout布局
  2. 查看Android中的AlarmManager事件
  3. Android多触点总结
  4. [Android]使用AdapterTypeRender对不同类型的item数据到UI的渲染
  5. Android(安卓)4.0系统触摸设备概述
  6. OpenGL ES2.0实现手指滑动平移、双指缩放Android
  7. Android(安卓)View的事件传递机制
  8. 深入研究Android事件传递与重绘
  9. android亮屏、黑屏、解锁事件的系统广播接收

随机推荐

  1. Android开发资料推荐之安卓巴士Android开
  2. 学习心得(二)
  3. Android启示录——开始Android旅途
  4. android的listView组件
  5. android Style样式
  6. Android的一些属性使用
  7. Android(安卓)语音输入API使用
  8. android表格布局
  9. 要学android不可不学的android样例
  10. 在Android(安卓)Studio中查看android APP