package com.marshalchen.common.uimodule.gesturedetectors; import android.content.Context; import android.graphics.PointF; import android.view.MotionEvent; /** * @author Almer Thie (code.almeros.com) * Copyright (c) 2013, Almer Thie (code.almeros.com) * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ public class MoveGestureDetector extends BaseGestureDetector { /** * Listener which must be implemented which is used by MoveGestureDetector * to perform callbacks to any implementing class which is registered to a * MoveGestureDetector via the constructor. * * @see com.marshalchen.common.uimodule.gesturedetectors.MoveGestureDetector.SimpleOnMoveGestureListener */ public interface OnMoveGestureListener { public boolean onMove(MoveGestureDetector detector); public boolean onMoveBegin(MoveGestureDetector detector); public void onMoveEnd(MoveGestureDetector detector); } /** * Helper class which may be extended and where the methods may be * implemented. This way it is not necessary to implement all methods * of OnMoveGestureListener. */ public static class SimpleOnMoveGestureListener implements OnMoveGestureListener { public boolean onMove(MoveGestureDetector detector) { return false; } public boolean onMoveBegin(MoveGestureDetector detector) { return true; } public void onMoveEnd(MoveGestureDetector detector) { // Do nothing, overridden implementation may be used } } private static final PointF FOCUS_DELTA_ZERO = new PointF(); private final OnMoveGestureListener mListener; private PointF mCurrFocusInternal; private PointF mPrevFocusInternal; private PointF mFocusExternal = new PointF(); private PointF mFocusDeltaExternal = new PointF(); public MoveGestureDetector(Context context, OnMoveGestureListener listener) { super(context); mListener = listener; } @Override protected void handleStartProgressEvent(int actionCode, MotionEvent event){ switch (actionCode) { case MotionEvent.ACTION_DOWN: resetState(); // In case we missed an UP/CANCEL event mPrevEvent = MotionEvent.obtain(event); mTimeDelta = 0; updateStateByEvent(event); break; case MotionEvent.ACTION_MOVE: mGestureInProgress = mListener.onMoveBegin(this); break; } } @Override protected void handleInProgressEvent(int actionCode, MotionEvent event){ switch (actionCode) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mListener.onMoveEnd(this); resetState(); break; case MotionEvent.ACTION_MOVE: updateStateByEvent(event); // Only accept the event if our relative pressure is within // a certain limit. This can help filter shaky data as a // finger is lifted. if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { final boolean updatePrevious = mListener.onMove(this); if (updatePrevious) { mPrevEvent.recycle(); mPrevEvent = MotionEvent.obtain(event); } } break; } } protected void updateStateByEvent(MotionEvent curr) { super.updateStateByEvent(curr); final MotionEvent prev = mPrevEvent; // Focus intenal mCurrFocusInternal = determineFocalPoint(curr); mPrevFocusInternal = determineFocalPoint(prev); // Focus external // - Prevent skipping of focus delta when a finger is added or removed boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount(); mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y); // - Don't directly use mFocusInternal (or skipping will occur). Add // unskipped delta values to mFocusExternal instead. mFocusExternal.x += mFocusDeltaExternal.x; mFocusExternal.y += mFocusDeltaExternal.y; } /** * Determine (multi)finger focal point (a.k.a. center point between all * fingers) * * @param MotionEvent e * @return PointF focal point */ private PointF determineFocalPoint(MotionEvent e){ // Number of fingers on screen final int pCount = e.getPointerCount(); float x = 0f; float y = 0f; for(int i = 0; i < pCount; i++){ x += e.getX(i); y += e.getY(i); } return new PointF(x/pCount, y/pCount); } public float getFocusX() { return mFocusExternal.x; } public float getFocusY() { return mFocusExternal.y; } public PointF getFocusDelta() { return mFocusDeltaExternal; } }