Improved gestures: parallel system and samples #253
This commit is contained in:
parent
71f7c45b21
commit
ba93445259
@ -3,6 +3,7 @@
|
||||
## New since 0.6.0
|
||||
|
||||
- Mapsforge multiple map files [#208](https://github.com/mapsforge/vtm/issues/208)
|
||||
- Improved gestures implementation [#253](https://github.com/mapsforge/vtm/issues/253)
|
||||
- Polygon label position enhancements [#80](https://github.com/mapsforge/vtm/issues/80)
|
||||
- vtm-web modules update [#51](https://github.com/mapsforge/vtm/issues/51)
|
||||
- Mapbox vector tiles [#57](https://github.com/mapsforge/vtm/issues/57)
|
||||
|
@ -59,6 +59,9 @@
|
||||
<activity
|
||||
android:name=".MultiMapActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||
<activity
|
||||
android:name=".NewGesturesActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||
<activity
|
||||
android:name=".OsmJsonMapActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 devemux86
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.android.test;
|
||||
|
||||
import org.oscim.map.Map;
|
||||
|
||||
public class NewGesturesActivity extends MarkerOverlayActivity {
|
||||
|
||||
public NewGesturesActivity() {
|
||||
super();
|
||||
Map.NEW_GESTURES = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
// Revert gestures for other activities
|
||||
Map.NEW_GESTURES = false;
|
||||
}
|
||||
}
|
@ -53,6 +53,7 @@ public class Samples extends Activity {
|
||||
linearLayout.addView(createButton(VectorLayerMapActivity.class));
|
||||
linearLayout.addView(createButton(MultiMapActivity.class));
|
||||
linearLayout.addView(createButton(MapFragmentActivity.class));
|
||||
linearLayout.addView(createButton(NewGesturesActivity.class));
|
||||
}
|
||||
|
||||
private Button createButton(final Class<?> clazz) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2012 Hannes Janetzek
|
||||
* Copyright 2016 devemux86
|
||||
*
|
||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||
*
|
||||
@ -21,11 +22,13 @@ import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.GestureDetector;
|
||||
|
||||
import org.oscim.android.canvas.AndroidGraphics;
|
||||
import org.oscim.android.gl.AndroidGL;
|
||||
import org.oscim.android.gl.GlConfigChooser;
|
||||
import org.oscim.android.input.AndroidMotionEvent;
|
||||
import org.oscim.android.input.GestureHandler;
|
||||
import org.oscim.backend.CanvasAdapter;
|
||||
import org.oscim.backend.GLAdapter;
|
||||
import org.oscim.map.Map;
|
||||
@ -51,6 +54,7 @@ public class MapView extends GLSurfaceView {
|
||||
}
|
||||
|
||||
protected final AndroidMap mMap;
|
||||
protected GestureDetector mGestureDetector;
|
||||
protected final AndroidMotionEvent mMotionEvent;
|
||||
|
||||
public MapView(Context context) {
|
||||
@ -91,6 +95,12 @@ public class MapView extends GLSurfaceView {
|
||||
mMap.clearMap();
|
||||
mMap.updateMap(false);
|
||||
|
||||
if (!Map.NEW_GESTURES) {
|
||||
GestureHandler gestureHandler = new GestureHandler(mMap);
|
||||
mGestureDetector = new GestureDetector(context, gestureHandler);
|
||||
mGestureDetector.setOnDoubleTapListener(gestureHandler);
|
||||
}
|
||||
|
||||
mMotionEvent = new AndroidMotionEvent();
|
||||
}
|
||||
|
||||
@ -112,6 +122,11 @@ public class MapView extends GLSurfaceView {
|
||||
if (!isClickable())
|
||||
return false;
|
||||
|
||||
if (mGestureDetector != null) {
|
||||
if (mGestureDetector.onTouchEvent(motionEvent))
|
||||
return true;
|
||||
}
|
||||
|
||||
mMap.input.fire(null, mMotionEvent.wrap(motionEvent));
|
||||
mMotionEvent.recycle();
|
||||
return true;
|
||||
|
97
vtm-android/src/org/oscim/android/input/GestureHandler.java
Normal file
97
vtm-android/src/org/oscim/android/input/GestureHandler.java
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2013 Hannes Janetzek
|
||||
* Copyright 2016 devemux86
|
||||
*
|
||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.android.input;
|
||||
|
||||
import android.view.GestureDetector.OnDoubleTapListener;
|
||||
import android.view.GestureDetector.OnGestureListener;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.map.Map;
|
||||
|
||||
public class GestureHandler implements OnGestureListener, OnDoubleTapListener {
|
||||
private final AndroidMotionEvent mMotionEvent;
|
||||
private final Map mMap;
|
||||
|
||||
// Quick scale (double tap + swipe)
|
||||
protected boolean quickScale;
|
||||
|
||||
public GestureHandler(Map map) {
|
||||
mMotionEvent = new AndroidMotionEvent();
|
||||
mMap = map;
|
||||
}
|
||||
|
||||
/* OnGestureListener */
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
// Quick scale (no long press)
|
||||
if (quickScale)
|
||||
return;
|
||||
|
||||
mMap.handleGesture(Gesture.LONG_PRESS, mMotionEvent.wrap(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
quickScale = false;
|
||||
|
||||
return mMap.handleGesture(Gesture.PRESS, mMotionEvent.wrap(e));
|
||||
}
|
||||
|
||||
/* OnDoubleTapListener */
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return mMap.handleGesture(Gesture.TAP, mMotionEvent.wrap(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
int action = e.getActionMasked();
|
||||
|
||||
// Quick scale
|
||||
quickScale = (action == MotionEvent.ACTION_MOVE);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mMap.handleGesture(Gesture.DOUBLE_TAP, mMotionEvent.wrap(e));
|
||||
}
|
||||
}
|
@ -85,11 +85,11 @@ public abstract class GdxMap implements ApplicationListener {
|
||||
mMapRenderer.onSurfaceChanged(w, h);
|
||||
|
||||
InputMultiplexer mux = new InputMultiplexer();
|
||||
mGestureDetector = new GestureDetector(new LayerHandler(mMap));
|
||||
mux.addProcessor(mGestureDetector);
|
||||
if (!Map.NEW_GESTURES) {
|
||||
mGestureDetector = new GestureDetector(new LayerHandler(mMap));
|
||||
mux.addProcessor(mGestureDetector);
|
||||
}
|
||||
mux.addProcessor(new InputHandler(this));
|
||||
//mux.addProcessor(new GestureDetector(20, 0.5f, 2, 0.05f,
|
||||
// new MapController(mMap)));
|
||||
mux.addProcessor(new MotionHandler(mMap));
|
||||
|
||||
Gdx.input.setInputProcessor(mux);
|
||||
|
@ -22,7 +22,6 @@ import com.badlogic.gdx.InputProcessor;
|
||||
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.utils.ArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
28
vtm-playground/src/org/oscim/test/NewGesturesTest.java
Normal file
28
vtm-playground/src/org/oscim/test/NewGesturesTest.java
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 devemux86
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.test;
|
||||
|
||||
import org.oscim.gdx.GdxMapApp;
|
||||
import org.oscim.map.Map;
|
||||
|
||||
public class NewGesturesTest extends MarkerLayerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map.NEW_GESTURES = true;
|
||||
|
||||
GdxMapApp.init();
|
||||
GdxMapApp.run(new NewGesturesTest());
|
||||
}
|
||||
}
|
@ -18,14 +18,13 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
public class MapEventLayerTest {
|
||||
private MapEventLayer layer;
|
||||
private Map mockMap;
|
||||
private ViewController mockViewport;
|
||||
private Animator mockAnimator;
|
||||
private ArgumentCaptor<Float> argumentCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockMap = Mockito.mock(Map.class);
|
||||
Map mockMap = Mockito.mock(Map.class);
|
||||
mockViewport = Mockito.mock(ViewController.class);
|
||||
mockAnimator = Mockito.mock(Animator.class);
|
||||
layer = new MapEventLayer(mockMap);
|
||||
@ -94,12 +93,12 @@ public class MapEventLayerTest {
|
||||
layer.onTouchEvent(new TestMotionEvent(MotionEvent.ACTION_UP, 1, 2));
|
||||
}
|
||||
|
||||
class TestMotionEvent extends MotionEvent {
|
||||
private class TestMotionEvent extends MotionEvent {
|
||||
final int action;
|
||||
final float x;
|
||||
final float y;
|
||||
|
||||
public TestMotionEvent(int action, float x, float y) {
|
||||
TestMotionEvent(int action, float x, float y) {
|
||||
this.action = action;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
@ -34,7 +34,7 @@ public interface Gesture {
|
||||
final class TripleTap implements Gesture {
|
||||
}
|
||||
|
||||
class TwoFingerTap implements Gesture {
|
||||
final class TwoFingerTap implements Gesture {
|
||||
}
|
||||
|
||||
Gesture PRESS = new Press();
|
||||
|
42
vtm/src/org/oscim/layers/AbstractMapEventLayer.java
Normal file
42
vtm/src/org/oscim/layers/AbstractMapEventLayer.java
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 devemux86
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.layers;
|
||||
|
||||
import org.oscim.map.Map;
|
||||
|
||||
public abstract class AbstractMapEventLayer extends Layer {
|
||||
|
||||
public AbstractMapEventLayer(Map map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public abstract void enableRotation(boolean enable);
|
||||
|
||||
public abstract boolean rotationEnabled();
|
||||
|
||||
public abstract void enableTilt(boolean enable);
|
||||
|
||||
public abstract boolean tiltEnabled();
|
||||
|
||||
public abstract void enableMove(boolean enable);
|
||||
|
||||
public abstract boolean moveEnabled();
|
||||
|
||||
public abstract void enableZoom(boolean enable);
|
||||
|
||||
public abstract boolean zoomEnabled();
|
||||
|
||||
public abstract void setFixOnCenter(boolean enable);
|
||||
}
|
@ -22,15 +22,11 @@ import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.event.Event;
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.event.GestureListener;
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.Map.InputListener;
|
||||
import org.oscim.map.ViewController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static org.oscim.backend.CanvasAdapter.dpi;
|
||||
import static org.oscim.utils.FastMath.withinSquaredDist;
|
||||
@ -41,9 +37,7 @@ import static org.oscim.utils.FastMath.withinSquaredDist;
|
||||
* TODO rewrite using gesture primitives to build more complex gestures:
|
||||
* maybe something similar to this https://github.com/ucbvislab/Proton
|
||||
*/
|
||||
public class MapEventLayer extends Layer implements InputListener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MapEventLayer.class);
|
||||
public class MapEventLayer extends AbstractMapEventLayer implements InputListener, GestureListener {
|
||||
|
||||
private boolean mEnableRotate = true;
|
||||
private boolean mEnableTilt = true;
|
||||
@ -62,12 +56,8 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
private boolean mDoTilt;
|
||||
|
||||
private boolean mDown;
|
||||
private boolean mDoubleTap;
|
||||
private boolean mDragZoom;
|
||||
private boolean mTwoFingers;
|
||||
private boolean mTwoFingersDone;
|
||||
private int mTaps;
|
||||
private long mStartDown;
|
||||
private MotionEvent mLastTap;
|
||||
|
||||
private float mPrevX1;
|
||||
private float mPrevY1;
|
||||
@ -89,30 +79,18 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
private static final float PINCH_ROTATE_THRESHOLD = 0.2f;
|
||||
private static final float PINCH_ROTATE_THRESHOLD2 = 0.5f;
|
||||
|
||||
//TODO Should be initialized with platform specific defaults
|
||||
/**
|
||||
* 100 ms since start of move to reduce fling scroll
|
||||
*/
|
||||
private static final long FLING_MIN_THRESHOLD = 100;
|
||||
private static final long DOUBLE_TAP_THRESHOLD = 300;
|
||||
private static final long LONG_PRESS_THRESHOLD = 500;
|
||||
private static final float FLING_MIN_THREHSHOLD = 100;
|
||||
|
||||
private final VelocityTracker mTracker;
|
||||
private final Timer mTimer;
|
||||
private TimerTask mTimerTask;
|
||||
|
||||
private final MapPosition mapPosition = new MapPosition();
|
||||
|
||||
public MapEventLayer(Map map) {
|
||||
super(map);
|
||||
mTracker = new VelocityTracker();
|
||||
mTimer = new Timer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
mTimer.cancel();
|
||||
mTimer.purge();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,34 +98,42 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
onTouchEvent(motionEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableRotation(boolean enable) {
|
||||
mEnableRotate = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rotationEnabled() {
|
||||
return mEnableRotate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableTilt(boolean enable) {
|
||||
mEnableTilt = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tiltEnabled() {
|
||||
return mEnableTilt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableMove(boolean enable) {
|
||||
mEnableMove = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveEnabled() {
|
||||
return mEnableMove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableZoom(boolean enable) {
|
||||
mEnableScale = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean zoomEnabled() {
|
||||
return mEnableScale;
|
||||
}
|
||||
@ -155,62 +141,29 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
/**
|
||||
* When enabled zoom- and rotation-gestures will not move the viewport.
|
||||
*/
|
||||
@Override
|
||||
public void setFixOnCenter(boolean enable) {
|
||||
mFixOnCenter = enable;
|
||||
}
|
||||
|
||||
boolean onTouchEvent(final MotionEvent e) {
|
||||
boolean onTouchEvent(MotionEvent e) {
|
||||
|
||||
int action = getAction(e);
|
||||
final long time = e.getTime();
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (mTimerTask != null) {
|
||||
mTimerTask.cancel();
|
||||
mTimer.purge();
|
||||
mTimerTask = null;
|
||||
}
|
||||
mMap.handleGesture(Gesture.PRESS, e);
|
||||
mDown = true;
|
||||
mStartDown = time;
|
||||
if (mTaps > 0) {
|
||||
float mx = e.getX(0) - mLastTap.getX();
|
||||
float my = e.getY(0) - mLastTap.getY();
|
||||
if (isMinimalMove(mx, my)) {
|
||||
mTaps = 0;
|
||||
log.debug("tap {} {}", mLastTap.getX(), mLastTap.getY());
|
||||
mMap.handleGesture(Gesture.TAP, mLastTap);
|
||||
}
|
||||
} else {
|
||||
mMap.animator().cancel();
|
||||
mMap.animator().cancel();
|
||||
|
||||
mStartMove = -1;
|
||||
mDragZoom = false;
|
||||
mTwoFingers = false;
|
||||
mTwoFingersDone = false;
|
||||
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mTwoFingers || mStartMove != -1)
|
||||
return;
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
log.debug("long press {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.LONG_PRESS, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, LONG_PRESS_THRESHOLD);
|
||||
}
|
||||
mStartMove = -1;
|
||||
mDoubleTap = false;
|
||||
mDragZoom = false;
|
||||
|
||||
mPrevX1 = e.getX(0);
|
||||
mPrevY1 = e.getY(0);
|
||||
|
||||
mDown = true;
|
||||
return true;
|
||||
}
|
||||
if (!mDown) {
|
||||
if (!(mDown || mDoubleTap)) {
|
||||
/* no down event received */
|
||||
return false;
|
||||
}
|
||||
@ -221,12 +174,17 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
}
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
mDown = false;
|
||||
if (mTimerTask != null) {
|
||||
mTimerTask.cancel();
|
||||
mTimer.purge();
|
||||
mTimerTask = null;
|
||||
}
|
||||
if (mStartMove > 0) {
|
||||
if (mDoubleTap && !mDragZoom) {
|
||||
float pivotX = 0, pivotY = 0;
|
||||
if (!mFixOnCenter) {
|
||||
pivotX = mPrevX1 - mMap.getWidth() / 2;
|
||||
pivotY = mPrevY1 - mMap.getHeight() / 2;
|
||||
}
|
||||
|
||||
/* handle double tap zoom */
|
||||
mMap.animator().animateZoom(300, 2, pivotX, pivotY);
|
||||
|
||||
} else if (mStartMove > 0) {
|
||||
/* handle fling gesture */
|
||||
mTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mTracker.getVelocityX();
|
||||
@ -234,86 +192,16 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
|
||||
/* reduce velocity for short moves */
|
||||
float t = e.getTime() - mStartMove;
|
||||
if (t < FLING_MIN_THRESHOLD) {
|
||||
t = t / FLING_MIN_THRESHOLD;
|
||||
if (t < FLING_MIN_THREHSHOLD) {
|
||||
t = t / FLING_MIN_THREHSHOLD;
|
||||
vy *= t * t;
|
||||
vx *= t * t;
|
||||
}
|
||||
doFling(vx, vy);
|
||||
}
|
||||
|
||||
if (time - mStartDown > LONG_PRESS_THRESHOLD) {
|
||||
log.debug(" not a tap");
|
||||
// this was not a tap
|
||||
mTaps = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mTaps > 0) {
|
||||
if ((time - mLastTap.getTime()) >= DOUBLE_TAP_THRESHOLD) {
|
||||
mTaps = 1;
|
||||
log.debug("tap {} {}", mLastTap.getX(), mLastTap.getY());
|
||||
mMap.handleGesture(Gesture.TAP, mLastTap);
|
||||
} else {
|
||||
mTaps += 1;
|
||||
}
|
||||
} else {
|
||||
mTaps = 1;
|
||||
}
|
||||
|
||||
if (mLastTap != null) {
|
||||
mLastTap.recycle();
|
||||
}
|
||||
mLastTap = e.copy();
|
||||
|
||||
if (mTaps == 3) {
|
||||
mTaps = 0;
|
||||
log.debug("triple tap {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.TRIPLE_TAP, e);
|
||||
} else if (mTaps == 2) {
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTaps = 0;
|
||||
if (mDragZoom)
|
||||
return;
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
log.debug("double tap {} {}", e.getX(), e.getY());
|
||||
if (!mMap.handleGesture(Gesture.DOUBLE_TAP, e)) {
|
||||
/* handle double tap zoom */
|
||||
final float pivotX = mFixOnCenter ? 0 : mPrevX1 - mMap.getWidth() / 2;
|
||||
final float pivotY = mFixOnCenter ? 0 : mPrevY1 - mMap.getHeight() / 2;
|
||||
mMap.animator().animateZoom(300, 2, pivotX, pivotY);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, DOUBLE_TAP_THRESHOLD);
|
||||
} else {
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTaps = 0;
|
||||
if (!mTwoFingers && mStartMove == -1) {
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
log.debug("tap {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.TAP, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, DOUBLE_TAP_THRESHOLD);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_CANCEL) {
|
||||
mTaps = 0;
|
||||
return false;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
@ -322,12 +210,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_POINTER_UP) {
|
||||
if (e.getPointerCount() == 2 && !mTwoFingersDone) {
|
||||
log.debug("two finger tap");
|
||||
if (!mMap.handleGesture(Gesture.TWO_FINGER_TAP, e)) {
|
||||
mMap.animator().animateZoom(300, 0.5, 0f, 0f);
|
||||
}
|
||||
}
|
||||
updateMulti(e);
|
||||
return true;
|
||||
}
|
||||
@ -355,7 +237,12 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
mPrevY1 = y1;
|
||||
|
||||
/* double-tap drag zoom */
|
||||
if (mTaps == 1) {
|
||||
if (mDoubleTap) {
|
||||
/* just ignore first move event to set mPrevX/Y */
|
||||
if (!mDown) {
|
||||
mDown = true;
|
||||
return;
|
||||
}
|
||||
if (!mDragZoom && !isMinimalMove(mx, my)) {
|
||||
mPrevX1 -= mx;
|
||||
mPrevY1 -= my;
|
||||
@ -418,7 +305,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
mCanScale = false;
|
||||
mCanRotate = false;
|
||||
mDoTilt = true;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -445,7 +331,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
/* start rotate, disable tilt */
|
||||
mDoRotate = true;
|
||||
mCanTilt = false;
|
||||
mTwoFingersDone = true;
|
||||
|
||||
mAngle = rad;
|
||||
} else if (!mDoScale) {
|
||||
@ -465,7 +350,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
mDoRotate = true;
|
||||
mCanRotate = true;
|
||||
mAngle = rad;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,7 +365,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
|
||||
mCanTilt = false;
|
||||
mDoScale = true;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
if (mDoScale || mDoRotate) {
|
||||
@ -530,8 +413,6 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
mPrevY1 = e.getY(0);
|
||||
|
||||
if (cnt == 2) {
|
||||
mTwoFingers = true;
|
||||
|
||||
mDoScale = false;
|
||||
mDoRotate = false;
|
||||
mDoTilt = false;
|
||||
@ -564,6 +445,15 @@ public class MapEventLayer extends Layer implements InputListener {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGesture(Gesture g, MotionEvent e) {
|
||||
if (g == Gesture.DOUBLE_TAP) {
|
||||
mDoubleTap = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class VelocityTracker {
|
||||
/* sample window, 200ms */
|
||||
private static final int MAX_MS = 200;
|
||||
|
643
vtm/src/org/oscim/layers/MapEventLayer2.java
Normal file
643
vtm/src/org/oscim/layers/MapEventLayer2.java
Normal file
@ -0,0 +1,643 @@
|
||||
/*
|
||||
* Copyright 2013 Hannes Janetzek
|
||||
* Copyright 2016 devemux86
|
||||
* Copyright 2016 Andrey Novikov
|
||||
*
|
||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.layers;
|
||||
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.event.Event;
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.Map.InputListener;
|
||||
import org.oscim.map.ViewController;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static org.oscim.backend.CanvasAdapter.dpi;
|
||||
import static org.oscim.utils.FastMath.withinSquaredDist;
|
||||
|
||||
/**
|
||||
* Changes Viewport by handling move, fling, scale, rotation and tilt gestures.
|
||||
* <p/>
|
||||
* TODO rewrite using gesture primitives to build more complex gestures:
|
||||
* maybe something similar to this https://github.com/ucbvislab/Proton
|
||||
*/
|
||||
public class MapEventLayer2 extends AbstractMapEventLayer implements InputListener {
|
||||
|
||||
private boolean mEnableRotate = true;
|
||||
private boolean mEnableTilt = true;
|
||||
private boolean mEnableMove = true;
|
||||
private boolean mEnableScale = true;
|
||||
private boolean mFixOnCenter = false;
|
||||
|
||||
/* possible state transitions */
|
||||
private boolean mCanScale;
|
||||
private boolean mCanRotate;
|
||||
private boolean mCanTilt;
|
||||
|
||||
/* current gesture state */
|
||||
private boolean mDoRotate;
|
||||
private boolean mDoScale;
|
||||
private boolean mDoTilt;
|
||||
|
||||
private boolean mDown;
|
||||
private boolean mDragZoom;
|
||||
private boolean mTwoFingers;
|
||||
private boolean mTwoFingersDone;
|
||||
private int mTaps;
|
||||
private long mStartDown;
|
||||
private MotionEvent mLastTap;
|
||||
|
||||
private float mPrevX1;
|
||||
private float mPrevY1;
|
||||
private float mPrevX2;
|
||||
private float mPrevY2;
|
||||
|
||||
private double mAngle;
|
||||
private double mPrevPinchWidth;
|
||||
private long mStartMove;
|
||||
|
||||
/**
|
||||
* 2mm as minimal distance to start move: dpi / 25.4
|
||||
*/
|
||||
private static final float MIN_SLOP = 25.4f / 2;
|
||||
|
||||
private static final float PINCH_ZOOM_THRESHOLD = MIN_SLOP / 2;
|
||||
private static final float PINCH_TILT_THRESHOLD = MIN_SLOP / 2;
|
||||
private static final float PINCH_TILT_SLOPE = 0.75f;
|
||||
private static final float PINCH_ROTATE_THRESHOLD = 0.2f;
|
||||
private static final float PINCH_ROTATE_THRESHOLD2 = 0.5f;
|
||||
|
||||
//TODO Should be initialized with platform specific defaults
|
||||
/**
|
||||
* 100 ms since start of move to reduce fling scroll
|
||||
*/
|
||||
private static final long FLING_MIN_THRESHOLD = 100;
|
||||
private static final long DOUBLE_TAP_THRESHOLD = 300;
|
||||
private static final long LONG_PRESS_THRESHOLD = 500;
|
||||
|
||||
private final VelocityTracker mTracker;
|
||||
private final Timer mTimer;
|
||||
private TimerTask mTimerTask;
|
||||
|
||||
private final MapPosition mapPosition = new MapPosition();
|
||||
|
||||
public MapEventLayer2(Map map) {
|
||||
super(map);
|
||||
mTracker = new VelocityTracker();
|
||||
mTimer = new Timer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
mTimer.cancel();
|
||||
mTimer.purge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputEvent(Event e, MotionEvent motionEvent) {
|
||||
onTouchEvent(motionEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableRotation(boolean enable) {
|
||||
mEnableRotate = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rotationEnabled() {
|
||||
return mEnableRotate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableTilt(boolean enable) {
|
||||
mEnableTilt = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tiltEnabled() {
|
||||
return mEnableTilt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableMove(boolean enable) {
|
||||
mEnableMove = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveEnabled() {
|
||||
return mEnableMove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableZoom(boolean enable) {
|
||||
mEnableScale = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean zoomEnabled() {
|
||||
return mEnableScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* When enabled zoom- and rotation-gestures will not move the viewport.
|
||||
*/
|
||||
@Override
|
||||
public void setFixOnCenter(boolean enable) {
|
||||
mFixOnCenter = enable;
|
||||
}
|
||||
|
||||
private boolean onTouchEvent(final MotionEvent e) {
|
||||
int action = getAction(e);
|
||||
final long time = e.getTime();
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (mTimerTask != null) {
|
||||
mTimerTask.cancel();
|
||||
mTimer.purge();
|
||||
mTimerTask = null;
|
||||
}
|
||||
mMap.handleGesture(Gesture.PRESS, e);
|
||||
mDown = true;
|
||||
mStartDown = time;
|
||||
if (mTaps > 0) {
|
||||
float mx = e.getX(0) - mLastTap.getX();
|
||||
float my = e.getY(0) - mLastTap.getY();
|
||||
if (isMinimalMove(mx, my)) {
|
||||
mTaps = 0;
|
||||
//log.debug("tap {} {}", mLastTap.getX(), mLastTap.getY());
|
||||
mMap.handleGesture(Gesture.TAP, mLastTap);
|
||||
}
|
||||
} else {
|
||||
mMap.animator().cancel();
|
||||
|
||||
mStartMove = -1;
|
||||
mDragZoom = false;
|
||||
mTwoFingers = false;
|
||||
mTwoFingersDone = false;
|
||||
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mTwoFingers || mStartMove != -1)
|
||||
return;
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//log.debug("long press {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.LONG_PRESS, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, LONG_PRESS_THRESHOLD);
|
||||
}
|
||||
|
||||
mPrevX1 = e.getX(0);
|
||||
mPrevY1 = e.getY(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (!mDown) {
|
||||
/* no down event received */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
onActionMove(e);
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
mDown = false;
|
||||
if (mTimerTask != null) {
|
||||
mTimerTask.cancel();
|
||||
mTimer.purge();
|
||||
mTimerTask = null;
|
||||
}
|
||||
if (mStartMove > 0) {
|
||||
/* handle fling gesture */
|
||||
mTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mTracker.getVelocityX();
|
||||
float vy = mTracker.getVelocityY();
|
||||
|
||||
/* reduce velocity for short moves */
|
||||
float t = e.getTime() - mStartMove;
|
||||
if (t < FLING_MIN_THRESHOLD) {
|
||||
t = t / FLING_MIN_THRESHOLD;
|
||||
vy *= t * t;
|
||||
vx *= t * t;
|
||||
}
|
||||
doFling(vx, vy);
|
||||
}
|
||||
|
||||
if (time - mStartDown > LONG_PRESS_THRESHOLD) {
|
||||
//log.debug(" not a tap");
|
||||
// this was not a tap
|
||||
mTaps = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mTaps > 0) {
|
||||
if ((time - mLastTap.getTime()) >= DOUBLE_TAP_THRESHOLD) {
|
||||
mTaps = 1;
|
||||
//log.debug("tap {} {}", mLastTap.getX(), mLastTap.getY());
|
||||
mMap.handleGesture(Gesture.TAP, mLastTap);
|
||||
} else {
|
||||
mTaps += 1;
|
||||
}
|
||||
} else {
|
||||
mTaps = 1;
|
||||
}
|
||||
|
||||
if (mLastTap != null) {
|
||||
mLastTap.recycle();
|
||||
}
|
||||
mLastTap = e.copy();
|
||||
|
||||
if (mTaps == 3) {
|
||||
mTaps = 0;
|
||||
//log.debug("triple tap {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.TRIPLE_TAP, e);
|
||||
} else if (mTaps == 2) {
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTaps = 0;
|
||||
if (mDragZoom)
|
||||
return;
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//log.debug("double tap {} {}", e.getX(), e.getY());
|
||||
if (!mMap.handleGesture(Gesture.DOUBLE_TAP, e)) {
|
||||
/* handle double tap zoom */
|
||||
final float pivotX = mFixOnCenter ? 0 : mPrevX1 - mMap.getWidth() / 2;
|
||||
final float pivotY = mFixOnCenter ? 0 : mPrevY1 - mMap.getHeight() / 2;
|
||||
mMap.animator().animateZoom(300, 2, pivotX, pivotY);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, DOUBLE_TAP_THRESHOLD);
|
||||
} else {
|
||||
mTimerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTaps = 0;
|
||||
if (!mTwoFingers && mStartMove == -1) {
|
||||
mMap.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//log.debug("tap {} {}", e.getX(), e.getY());
|
||||
mMap.handleGesture(Gesture.TAP, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
mTimer.schedule(mTimerTask, DOUBLE_TAP_THRESHOLD);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_CANCEL) {
|
||||
mTaps = 0;
|
||||
return false;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
mStartMove = -1;
|
||||
updateMulti(e);
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_POINTER_UP) {
|
||||
if (e.getPointerCount() == 2 && !mTwoFingersDone) {
|
||||
//log.debug("two finger tap");
|
||||
if (!mMap.handleGesture(Gesture.TWO_FINGER_TAP, e)) {
|
||||
mMap.animator().animateZoom(300, 0.5, 0f, 0f);
|
||||
}
|
||||
}
|
||||
updateMulti(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int getAction(MotionEvent e) {
|
||||
return e.getAction() & MotionEvent.ACTION_MASK;
|
||||
}
|
||||
|
||||
private void onActionMove(MotionEvent e) {
|
||||
ViewController mViewport = mMap.viewport();
|
||||
float x1 = e.getX(0);
|
||||
float y1 = e.getY(0);
|
||||
|
||||
float mx = x1 - mPrevX1;
|
||||
float my = y1 - mPrevY1;
|
||||
|
||||
float width = mMap.getWidth();
|
||||
float height = mMap.getHeight();
|
||||
|
||||
if (e.getPointerCount() < 2) {
|
||||
mPrevX1 = x1;
|
||||
mPrevY1 = y1;
|
||||
|
||||
/* double-tap drag zoom */
|
||||
if (mTaps == 1) {
|
||||
if (!mDragZoom && !isMinimalMove(mx, my)) {
|
||||
mPrevX1 -= mx;
|
||||
mPrevY1 -= my;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO limit scale properly
|
||||
mDragZoom = true;
|
||||
mViewport.scaleMap(1 + my / (height / 6), 0, 0);
|
||||
mMap.updateMap(true);
|
||||
mStartMove = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* simple move */
|
||||
if (!mEnableMove)
|
||||
return;
|
||||
|
||||
if (mStartMove < 0) {
|
||||
if (!isMinimalMove(mx, my)) {
|
||||
mPrevX1 -= mx;
|
||||
mPrevY1 -= my;
|
||||
return;
|
||||
}
|
||||
|
||||
mStartMove = e.getTime();
|
||||
mTracker.start(x1, y1, mStartMove);
|
||||
return;
|
||||
}
|
||||
mViewport.moveMap(mx, my);
|
||||
mTracker.update(x1, y1, e.getTime());
|
||||
mMap.updateMap(true);
|
||||
if (mMap.viewport().getMapPosition(mapPosition))
|
||||
mMap.events.fire(Map.MOVE_EVENT, mapPosition);
|
||||
return;
|
||||
}
|
||||
mStartMove = -1;
|
||||
|
||||
float x2 = e.getX(1);
|
||||
float y2 = e.getY(1);
|
||||
float dx = (x1 - x2);
|
||||
float dy = (y1 - y2);
|
||||
|
||||
double rotateBy = 0;
|
||||
float scaleBy = 1;
|
||||
float tiltBy = 0;
|
||||
|
||||
mx = ((x1 + x2) - (mPrevX1 + mPrevX2)) / 2;
|
||||
my = ((y1 + y2) - (mPrevY1 + mPrevY2)) / 2;
|
||||
|
||||
if (mCanTilt) {
|
||||
float slope = (dx == 0) ? 0 : dy / dx;
|
||||
|
||||
if (Math.abs(slope) < PINCH_TILT_SLOPE) {
|
||||
|
||||
if (mDoTilt) {
|
||||
tiltBy = my / 5;
|
||||
} else if (Math.abs(my) > (dpi / PINCH_TILT_THRESHOLD)) {
|
||||
/* enter exclusive tilt mode */
|
||||
mCanScale = false;
|
||||
mCanRotate = false;
|
||||
mDoTilt = true;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double pinchWidth = Math.sqrt(dx * dx + dy * dy);
|
||||
double deltaPinch = pinchWidth - mPrevPinchWidth;
|
||||
|
||||
if (mCanRotate) {
|
||||
double rad = Math.atan2(dy, dx);
|
||||
double r = rad - mAngle;
|
||||
|
||||
if (mDoRotate) {
|
||||
double da = rad - mAngle;
|
||||
|
||||
if (Math.abs(da) > 0.0001) {
|
||||
rotateBy = da;
|
||||
mAngle = rad;
|
||||
|
||||
deltaPinch = 0;
|
||||
}
|
||||
} else {
|
||||
r = Math.abs(r);
|
||||
if (r > PINCH_ROTATE_THRESHOLD) {
|
||||
/* start rotate, disable tilt */
|
||||
mDoRotate = true;
|
||||
mCanTilt = false;
|
||||
mTwoFingersDone = true;
|
||||
|
||||
mAngle = rad;
|
||||
} else if (!mDoScale) {
|
||||
/* reduce pinch trigger by the amount of rotation */
|
||||
deltaPinch *= 1 - (r / PINCH_ROTATE_THRESHOLD);
|
||||
} else {
|
||||
mPrevPinchWidth = pinchWidth;
|
||||
}
|
||||
}
|
||||
} else if (mDoScale && mEnableRotate) {
|
||||
/* re-enable rotation when higher threshold is reached */
|
||||
double rad = Math.atan2(dy, dx);
|
||||
double r = rad - mAngle;
|
||||
|
||||
if (r > PINCH_ROTATE_THRESHOLD2) {
|
||||
/* start rotate again */
|
||||
mDoRotate = true;
|
||||
mCanRotate = true;
|
||||
mAngle = rad;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanScale || mDoRotate) {
|
||||
if (!(mDoScale || mDoRotate)) {
|
||||
/* enter exclusive scale mode */
|
||||
if (Math.abs(deltaPinch) > (dpi / PINCH_ZOOM_THRESHOLD)) {
|
||||
|
||||
if (!mDoRotate) {
|
||||
mPrevPinchWidth = pinchWidth;
|
||||
mCanRotate = false;
|
||||
}
|
||||
|
||||
mCanTilt = false;
|
||||
mDoScale = true;
|
||||
mTwoFingersDone = true;
|
||||
}
|
||||
}
|
||||
if (mDoScale || mDoRotate) {
|
||||
scaleBy = (float) (pinchWidth / mPrevPinchWidth);
|
||||
mPrevPinchWidth = pinchWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(mDoRotate || mDoScale || mDoTilt))
|
||||
return;
|
||||
|
||||
float pivotX = 0, pivotY = 0;
|
||||
|
||||
if (!mFixOnCenter) {
|
||||
pivotX = (x2 + x1) / 2 - width / 2;
|
||||
pivotY = (y2 + y1) / 2 - height / 2;
|
||||
}
|
||||
|
||||
synchronized (mViewport) {
|
||||
if (!mDoTilt) {
|
||||
if (rotateBy != 0)
|
||||
mViewport.rotateMap(rotateBy, pivotX, pivotY);
|
||||
if (scaleBy != 1)
|
||||
mViewport.scaleMap(scaleBy, pivotX, pivotY);
|
||||
|
||||
if (!mFixOnCenter)
|
||||
mViewport.moveMap(mx, my);
|
||||
} else {
|
||||
if (tiltBy != 0 && mViewport.tiltMap(-tiltBy))
|
||||
mViewport.moveMap(0, my / 2);
|
||||
}
|
||||
}
|
||||
|
||||
mPrevX1 = x1;
|
||||
mPrevY1 = y1;
|
||||
mPrevX2 = x2;
|
||||
mPrevY2 = y2;
|
||||
|
||||
mMap.updateMap(true);
|
||||
}
|
||||
|
||||
private void updateMulti(MotionEvent e) {
|
||||
int cnt = e.getPointerCount();
|
||||
|
||||
mPrevX1 = e.getX(0);
|
||||
mPrevY1 = e.getY(0);
|
||||
|
||||
if (cnt == 2) {
|
||||
mTwoFingers = true;
|
||||
|
||||
mDoScale = false;
|
||||
mDoRotate = false;
|
||||
mDoTilt = false;
|
||||
mCanScale = mEnableScale;
|
||||
mCanRotate = mEnableRotate;
|
||||
mCanTilt = mEnableTilt;
|
||||
|
||||
mPrevX2 = e.getX(1);
|
||||
mPrevY2 = e.getY(1);
|
||||
double dx = mPrevX1 - mPrevX2;
|
||||
double dy = mPrevY1 - mPrevY2;
|
||||
|
||||
mAngle = Math.atan2(dy, dx);
|
||||
mPrevPinchWidth = Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMinimalMove(float mx, float my) {
|
||||
float minSlop = (dpi / MIN_SLOP);
|
||||
return !withinSquaredDist(mx, my, minSlop * minSlop);
|
||||
}
|
||||
|
||||
private boolean doFling(float velocityX, float velocityY) {
|
||||
|
||||
int w = Tile.SIZE * 5;
|
||||
int h = Tile.SIZE * 5;
|
||||
|
||||
mMap.animator().animateFling(velocityX * 2, velocityY * 2,
|
||||
-w, w, -h, h);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class VelocityTracker {
|
||||
/* sample window, 200ms */
|
||||
private static final int MAX_MS = 200;
|
||||
private static final int SAMPLES = 32;
|
||||
|
||||
private float mLastX, mLastY;
|
||||
private long mLastTime;
|
||||
private int mNumSamples;
|
||||
private int mIndex;
|
||||
|
||||
private float[] mMeanX = new float[SAMPLES];
|
||||
private float[] mMeanY = new float[SAMPLES];
|
||||
private int[] mMeanTime = new int[SAMPLES];
|
||||
|
||||
public void start(float x, float y, long time) {
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
mNumSamples = 0;
|
||||
mIndex = SAMPLES;
|
||||
mLastTime = time;
|
||||
}
|
||||
|
||||
public void update(float x, float y, long time) {
|
||||
if (time == mLastTime)
|
||||
return;
|
||||
|
||||
if (--mIndex < 0)
|
||||
mIndex = SAMPLES - 1;
|
||||
|
||||
mMeanX[mIndex] = x - mLastX;
|
||||
mMeanY[mIndex] = y - mLastY;
|
||||
mMeanTime[mIndex] = (int) (time - mLastTime);
|
||||
|
||||
mLastTime = time;
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
|
||||
mNumSamples++;
|
||||
}
|
||||
|
||||
private float getVelocity(float[] move) {
|
||||
mNumSamples = Math.min(SAMPLES, mNumSamples);
|
||||
|
||||
double duration = 0;
|
||||
double amount = 0;
|
||||
|
||||
for (int c = 0; c < mNumSamples; c++) {
|
||||
int index = (mIndex + c) % SAMPLES;
|
||||
|
||||
float d = mMeanTime[index];
|
||||
if (c > 0 && duration + d > MAX_MS)
|
||||
break;
|
||||
|
||||
duration += d;
|
||||
amount += move[index] * (d / duration);
|
||||
}
|
||||
|
||||
if (duration == 0)
|
||||
return 0;
|
||||
|
||||
return (float) ((amount * 1000) / duration);
|
||||
}
|
||||
|
||||
float getVelocityY() {
|
||||
return getVelocity(mMeanY);
|
||||
}
|
||||
|
||||
float getVelocityX() {
|
||||
return getVelocity(mMeanX);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,8 +27,10 @@ import org.oscim.event.EventDispatcher;
|
||||
import org.oscim.event.EventListener;
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.layers.AbstractMapEventLayer;
|
||||
import org.oscim.layers.Layer;
|
||||
import org.oscim.layers.MapEventLayer;
|
||||
import org.oscim.layers.MapEventLayer2;
|
||||
import org.oscim.layers.tile.TileLayer;
|
||||
import org.oscim.layers.tile.vector.OsmTileLayer;
|
||||
import org.oscim.layers.tile.vector.VectorTileLayer;
|
||||
@ -45,7 +47,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class Map implements TaskQueue {
|
||||
|
||||
static final Logger log = LoggerFactory.getLogger(Map.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(Map.class);
|
||||
|
||||
/**
|
||||
* If true the {@link MapEventLayer2} will be used instead of default {@link MapEventLayer}.
|
||||
*/
|
||||
public static boolean NEW_GESTURES = false;
|
||||
|
||||
/**
|
||||
* Listener interface for map update notifications.
|
||||
@ -105,7 +112,7 @@ public abstract class Map implements TaskQueue {
|
||||
protected final Animator mAnimator;
|
||||
protected final MapPosition mMapPosition;
|
||||
|
||||
protected final MapEventLayer mEventLayer;
|
||||
protected final AbstractMapEventLayer mEventLayer;
|
||||
|
||||
protected boolean mClearMap = true;
|
||||
|
||||
@ -134,12 +141,15 @@ public abstract class Map implements TaskQueue {
|
||||
mAsyncExecutor = new AsyncExecutor(4, this);
|
||||
mMapPosition = new MapPosition();
|
||||
|
||||
mEventLayer = new MapEventLayer(this);
|
||||
if (NEW_GESTURES)
|
||||
mEventLayer = new MapEventLayer2(this);
|
||||
else
|
||||
mEventLayer = new MapEventLayer(this);
|
||||
mLayers.add(0, mEventLayer);
|
||||
|
||||
}
|
||||
|
||||
public MapEventLayer getEventLayer() {
|
||||
public AbstractMapEventLayer getEventLayer() {
|
||||
return mEventLayer;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user