package net.osmand.core.samples.android.sample1; import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; import net.osmand.util.MapUtils; import java.lang.reflect.Method; public class MultiTouchSupport { private static final String TAG = "MultiTouchSupport"; public static final int ACTION_MASK = 255; protected final Context ctx; private final MultiTouchZoomListener listener; protected Method getPointerCount; protected Method getX; protected Method getY; protected Method getPointerId; private float initialAngle; private float rotation; private static final float ROTATION_THRESHOLD_DEG = 15.0f; private boolean isRotating; private boolean multiTouchAPISupported = false; private boolean inTiltMode = false; private PointF firstFingerStart = new PointF(); private PointF secondFingerStart = new PointF(); private static final int TILT_X_THRESHOLD_PX = 40; private static final int TILT_Y_THRESHOLD_PX = 40; private static final int TILT_DY_THRESHOLD_PX = 40; private boolean inZoomMode = false; private float initialDistance = 100; private float scale = 1; private PointF centerPoint = new PointF(); private boolean multiTouch = false; public MultiTouchSupport(Context ctx, MultiTouchZoomListener listener) { this.ctx = ctx; this.listener = listener; initMethods(); } public boolean isMultiTouchSupported(){ return multiTouchAPISupported; } public boolean isInZoomMode(){ return inZoomMode; } public boolean isInTiltMode() { return inTiltMode; } public boolean isInMultiTouch() { return multiTouch; } private void initMethods() { try { getPointerCount = MotionEvent.class.getMethod("getPointerCount"); //$NON-NLS-1$ getPointerId = MotionEvent.class.getMethod("getPointerId", Integer.TYPE); //$NON-NLS-1$ getX = MotionEvent.class.getMethod("getX", Integer.TYPE); //$NON-NLS-1$ getY = MotionEvent.class.getMethod("getY", Integer.TYPE); //$NON-NLS-1$ multiTouchAPISupported = true; } catch (Exception e) { multiTouchAPISupported = false; } } public boolean onTouchEvent(MotionEvent event){ if(!isMultiTouchSupported()){ return false; } int actionCode = event.getAction() & ACTION_MASK; try { if (actionCode == MotionEvent.ACTION_UP || actionCode == MotionEvent.ACTION_CANCEL) { multiTouch = false; } Integer pointCount = (Integer) getPointerCount.invoke(event); if (pointCount < 2) { if (inZoomMode || inTiltMode) { listener.onGestureFinished(scale, rotation); inZoomMode = false; inTiltMode = false; } return multiTouch; } else { multiTouch = true; } Float x1 = (Float) getX.invoke(event, 0); Float x2 = (Float) getX.invoke(event, 1); Float y1 = (Float) getY.invoke(event, 0); Float y2 = (Float) getY.invoke(event, 1); float distance = (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); float angle = 0; boolean angleDefined = false; if (x1.floatValue() != x2.floatValue() || y1.floatValue() != y2.floatValue()) { angleDefined = true; angle = (float) (Math.atan2(y2 - y1, x2 -x1) * 180 / Math.PI); } switch (actionCode) { case MotionEvent.ACTION_POINTER_DOWN: { centerPoint = new PointF((x1 + x2) / 2, (y1 + y2) / 2); firstFingerStart = new PointF(x1, y1); secondFingerStart = new PointF(x2, y2); listener.onGestureInit(x1, y1, x2, y2); return true; } case MotionEvent.ACTION_POINTER_UP: { if (inZoomMode || inTiltMode) { listener.onGestureFinished(scale, rotation); inZoomMode = false; inTiltMode = false; } return true; } case MotionEvent.ACTION_MOVE: { if (inZoomMode) { // Keep zoom center fixed or flexible centerPoint = new PointF((x1 + x2) / 2, (y1 + y2) / 2); if (angleDefined) { float a = MapUtils.unifyRotationTo360(angle - initialAngle); if (!isRotating && Math.abs(a) > ROTATION_THRESHOLD_DEG) { isRotating = true; initialAngle = angle; } else if (isRotating) { rotation = a; } } scale = distance / initialDistance; listener.onZoomingOrRotating(scale, rotation); return true; } else if (inTiltMode) { float dy2 = secondFingerStart.y - y2; float viewAngle = dy2 / 8f; listener.onChangingViewAngle(viewAngle); } else { float dx1 = Math.abs(firstFingerStart.x - x1); float dx2 = Math.abs(secondFingerStart.x - x2); float dy1 = Math.abs(firstFingerStart.y - y1); float dy2 = Math.abs(secondFingerStart.y - y2); float startDy = Math.abs(secondFingerStart.y - firstFingerStart.y); if (dx1 < TILT_X_THRESHOLD_PX && dx2 < TILT_X_THRESHOLD_PX && dy1 > TILT_Y_THRESHOLD_PX && dy2 > TILT_Y_THRESHOLD_PX && startDy < TILT_Y_THRESHOLD_PX * 6 && Math.abs(dy2 - dy1) < TILT_DY_THRESHOLD_PX) { listener.onChangeViewAngleStarted(); inTiltMode = true; } else if (dx1 > TILT_X_THRESHOLD_PX || dx2 > TILT_X_THRESHOLD_PX || Math.abs(dy2 - dy1) > TILT_DY_THRESHOLD_PX || Math.abs(dy1 - dy2) > TILT_DY_THRESHOLD_PX) { listener.onZoomStarted(centerPoint); initialDistance = distance; initialAngle = angle; rotation = 0; scale = 0; isRotating = false; inZoomMode = true; } } } default: break; } } catch (Exception e) { Log.e(TAG, "Multi touch exception" , e); //$NON-NLS-1$ } return true; } public PointF getCenterPoint() { return centerPoint; } public interface MultiTouchZoomListener { void onZoomStarted(PointF centerPoint); void onZoomingOrRotating(float scale, float rotation); void onChangeViewAngleStarted(); void onChangingViewAngle(float angle); void onGestureFinished(float scale, float rotation); void onGestureInit(float x1, float y1, float x2, float y2); } }