From ba52bfddbe50f00874a08f54061a37ce5474b19f Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Wed, 4 Dec 2013 13:09:23 +0100 Subject: [PATCH] add pluggable gesture detection - extract inner Map.Layers class - extract inner MapView.Map class -> AndroidMap --- .../src/org/oscim/android/AndroidMap.java | 109 ++++++++++++ .../src/org/oscim/android/MapView.java | 159 ++++++++---------- .../android/input/AndroidMotionEvent.java | 3 +- vtm/src/org/oscim/core/Task.java | 23 +++ vtm/src/org/oscim/event/Gesture.java | 21 +++ vtm/src/org/oscim/event/GestureDetector.java | 20 +++ vtm/src/org/oscim/event/GestureListener.java | 7 + vtm/src/org/oscim/event/TouchListener.java | 11 -- .../layers/marker/ItemizedIconLayer.java | 70 ++++---- vtm/src/org/oscim/map/Layers.java | 149 ++++++++++++++++ vtm/src/org/oscim/map/Map.java | 154 +++-------------- 11 files changed, 465 insertions(+), 261 deletions(-) create mode 100644 vtm-android/src/org/oscim/android/AndroidMap.java create mode 100644 vtm/src/org/oscim/core/Task.java create mode 100644 vtm/src/org/oscim/event/Gesture.java create mode 100644 vtm/src/org/oscim/event/GestureDetector.java create mode 100644 vtm/src/org/oscim/event/GestureListener.java delete mode 100644 vtm/src/org/oscim/event/TouchListener.java create mode 100644 vtm/src/org/oscim/map/Layers.java diff --git a/vtm-android/src/org/oscim/android/AndroidMap.java b/vtm-android/src/org/oscim/android/AndroidMap.java new file mode 100644 index 00000000..08df96c5 --- /dev/null +++ b/vtm-android/src/org/oscim/android/AndroidMap.java @@ -0,0 +1,109 @@ +/* + * Copyright 2013 + * + * 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 . + */ +package org.oscim.android; + +import org.oscim.map.Map; + +import android.widget.RelativeLayout.LayoutParams; + +public class AndroidMap extends Map { + + private final MapView mMapView; + boolean mWaitRedraw; + final GLView mGLView; + boolean mPausing = false; + + private final Runnable mRedrawRequest = new Runnable() { + @Override + public void run() { + mWaitRedraw = false; + redrawMapInternal(false); + } + }; + + public AndroidMap(MapView mapView) { + super(); + + mMapView = mapView; + mGLView = new GLView(mapView.getContext(), this); + + LayoutParams params = + new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + + mapView.addView(mGLView, params); + + //mGestureDetector = + } + + @Override + public int getWidth() { + return mMapView.getWidth(); + } + + @Override + public int getHeight() { + return mMapView.getHeight(); + } + + @Override + public void updateMap(boolean requestRender) { + if (requestRender && !mClearMap && !mPausing) // && mInitialized) + mGLView.requestRender(); + + if (!mWaitRedraw) { + mWaitRedraw = true; + mMapView.post(mRedrawRequest); + } + } + + @Override + public void render() { + if (mClearMap) + updateMap(false); + else + mGLView.requestRender(); + } + + void redrawMapInternal(boolean forceRedraw) { + boolean clear = mClearMap; + + if (forceRedraw && !clear) + mGLView.requestRender(); + + updateLayers(); + + if (clear) { + mGLView.requestRender(); + mClearMap = false; + } + } + + @Override + public boolean post(Runnable runnable) { + return mMapView.post(runnable); + } + + @Override + public boolean postDelayed(Runnable action, long delay) { + return mMapView.postDelayed(action, delay); + } + + + public void pause(boolean pause) { + mPausing = pause; + } + +} diff --git a/vtm-android/src/org/oscim/android/MapView.java b/vtm-android/src/org/oscim/android/MapView.java index 32c61a93..d6ae7818 100644 --- a/vtm-android/src/org/oscim/android/MapView.java +++ b/vtm-android/src/org/oscim/android/MapView.java @@ -22,6 +22,7 @@ import org.oscim.backend.AssetAdapter; import org.oscim.backend.CanvasAdapter; import org.oscim.backend.GLAdapter; import org.oscim.core.Tile; +import org.oscim.event.Gesture; import org.oscim.map.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,9 @@ import org.slf4j.LoggerFactory; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; @@ -40,6 +44,10 @@ public class MapView extends RelativeLayout { static final Logger log = LoggerFactory.getLogger(MapView.class); + static { + System.loadLibrary("vtm-jni"); + } + public static final boolean debugFrameTime = false; public static final boolean testRegionZoom = false; @@ -48,21 +56,15 @@ public class MapView extends RelativeLayout { public boolean enablePagedFling = false; private final Compass mCompass; + private final GestureDetector mGestureDetector; private int mWidth; private int mHeight; - private final Map mMap; + final AndroidMap mMap; - final GLView mGLView; - boolean mPausing = false; boolean mInitialized = false; - static { - System.loadLibrary("vtm-jni"); - //System.loadLibrary("tessellate"); - } - /** * @param context * the enclosing MapActivity instance. @@ -87,12 +89,9 @@ public class MapView extends RelativeLayout { public MapView(Context context, AttributeSet attributeSet) { super(context, attributeSet); - if (!(context instanceof MapActivity)) { - throw new IllegalArgumentException( - "context is not an instance of MapActivity"); - } + if (!(context instanceof MapActivity)) + throw new IllegalArgumentException("context is not an instance of MapActivity"); - //Log.logger = new AndroidLog(); CanvasAdapter.g = AndroidGraphics.INSTANCE; AssetAdapter.g = new AndroidAssetAdapter(context); GLAdapter.g = new AndroidGL(); @@ -109,87 +108,64 @@ public class MapView extends RelativeLayout { MapActivity mapActivity = (MapActivity) context; - final MapView m = this; + mMap = new AndroidMap(this); - mMap = new Map() { - - boolean mWaitRedraw; - - private final Runnable mRedrawRequest = new Runnable() { - @Override - public void run() { - mWaitRedraw = false; - redrawMapInternal(false); - } - }; - - @Override - public int getWidth() { - return m.getWidth(); - } - - @Override - public int getHeight() { - return m.getHeight(); - } - - @Override - public void updateMap(boolean requestRender) { - if (requestRender && !mClearMap && !mPausing && mInitialized) - mGLView.requestRender(); - - if (!mWaitRedraw) { - mWaitRedraw = true; - getView().post(mRedrawRequest); - } - } - - @Override - public void render() { - if (mClearMap) - updateMap(false); - else - mGLView.requestRender(); - } - - void redrawMapInternal(boolean forceRedraw) { - boolean clear = mClearMap; - - if (forceRedraw && !clear) - mGLView.requestRender(); - - updateLayers(); - - if (clear) { - mGLView.requestRender(); - mClearMap = false; - } - } - - @Override - public boolean post(Runnable runnable) { - return getView().post(runnable); - } - - @Override - public boolean postDelayed(Runnable action, long delay) { - return getView().postDelayed(action, delay); - } - }; - - mGLView = new GLView(context, mMap); mCompass = new Compass(mapActivity, mMap); mapActivity.registerMapView(mMap); - LayoutParams params = new LayoutParams( - android.view.ViewGroup.LayoutParams.MATCH_PARENT, - android.view.ViewGroup.LayoutParams.MATCH_PARENT); - - addView(mGLView, params); - mMap.clearMap(); mMap.updateMap(false); + + mGestureDetector = new GestureDetector(context, new OnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return mMap.handleGesture(Gesture.TAP, mMotionEvent.wrap(e)); + } + + @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) { + 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) { + return mMap.handleGesture(Gesture.PRESS, mMotionEvent.wrap(e)); + } + }); + + //mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() { + // + // @Override + // public boolean onSingleTapConfirmed(MotionEvent e) { + // return false; + // } + // + // @Override + // public boolean onDoubleTapEvent(MotionEvent e) { + // return false; + // } + // + // @Override + // public boolean onDoubleTap(MotionEvent e) { + // return false; + // } + //}); + } View getView() { @@ -206,7 +182,7 @@ public class MapView extends RelativeLayout { } void onPause() { - mPausing = true; + mMap.pause(true); if (this.mCompassEnabled) mCompass.disable(); @@ -217,10 +193,10 @@ public class MapView extends RelativeLayout { if (this.mCompassEnabled) mCompass.enable(); - mPausing = false; + mMap.pause(false); } - AndroidMotionEvent mMotionEvent = new AndroidMotionEvent(); + final AndroidMotionEvent mMotionEvent = new AndroidMotionEvent(); @Override public boolean onTouchEvent(android.view.MotionEvent motionEvent) { @@ -228,6 +204,9 @@ public class MapView extends RelativeLayout { if (!isClickable()) return false; + if (mGestureDetector.onTouchEvent(motionEvent)) + return true; + mMotionEvent.wrap(motionEvent); mMap.handleMotionEvent(mMotionEvent); diff --git a/vtm-android/src/org/oscim/android/input/AndroidMotionEvent.java b/vtm-android/src/org/oscim/android/input/AndroidMotionEvent.java index c26fffe4..06e22eca 100644 --- a/vtm-android/src/org/oscim/android/input/AndroidMotionEvent.java +++ b/vtm-android/src/org/oscim/android/input/AndroidMotionEvent.java @@ -20,8 +20,9 @@ public class AndroidMotionEvent extends MotionEvent { android.view.MotionEvent mEvent; - public void wrap(android.view.MotionEvent e) { + public MotionEvent wrap(android.view.MotionEvent e) { mEvent = e; + return this; } @Override diff --git a/vtm/src/org/oscim/core/Task.java b/vtm/src/org/oscim/core/Task.java new file mode 100644 index 00000000..b9f47b6b --- /dev/null +++ b/vtm/src/org/oscim/core/Task.java @@ -0,0 +1,23 @@ +package org.oscim.core; + +public abstract class Task implements Runnable { + + boolean isCanceled; + + @Override + public void run() { + run(isCanceled); + } + + public void run(boolean canceled) { + + } + + public void cancel() { + isCanceled = true; + } + + public void reset() { + isCanceled = false; + } +} diff --git a/vtm/src/org/oscim/event/Gesture.java b/vtm/src/org/oscim/event/Gesture.java new file mode 100644 index 00000000..3804dc5d --- /dev/null +++ b/vtm/src/org/oscim/event/Gesture.java @@ -0,0 +1,21 @@ +package org.oscim.event; + +public interface Gesture { + + static final class Press implements Gesture { + } + + static final class LongPress implements Gesture { + } + + static final class Tap implements Gesture { + } + + static final class DoubleTap implements Gesture { + } + + public static Gesture PRESS = new Press(); + public static Gesture LONG_PRESS = new LongPress(); + public static Gesture TAP = new Tap(); + public static Gesture DOUBLE_TAP = new DoubleTap(); +} diff --git a/vtm/src/org/oscim/event/GestureDetector.java b/vtm/src/org/oscim/event/GestureDetector.java new file mode 100644 index 00000000..f03928af --- /dev/null +++ b/vtm/src/org/oscim/event/GestureDetector.java @@ -0,0 +1,20 @@ +package org.oscim.event; + +import org.oscim.map.Map; + +public class GestureDetector { + + private final Map mMap; + + public GestureDetector(Map map) { + mMap = map; + } + + public boolean onTouchEvent(MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_DOWN) { + return mMap.handleGesture(Gesture.PRESS, e); + } + + return false; + } +} diff --git a/vtm/src/org/oscim/event/GestureListener.java b/vtm/src/org/oscim/event/GestureListener.java new file mode 100644 index 00000000..ce904f1d --- /dev/null +++ b/vtm/src/org/oscim/event/GestureListener.java @@ -0,0 +1,7 @@ +package org.oscim.event; + +public interface GestureListener { + + boolean onGesture(Gesture g, MotionEvent e); + +} diff --git a/vtm/src/org/oscim/event/TouchListener.java b/vtm/src/org/oscim/event/TouchListener.java deleted file mode 100644 index d85c3ce4..00000000 --- a/vtm/src/org/oscim/event/TouchListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.oscim.event; - -import org.oscim.core.MapPosition; - -public interface TouchListener { - boolean onPress(MotionEvent e, MapPosition pos); - - boolean onLongPress(MotionEvent e, MapPosition pos); - - boolean onTap(MotionEvent e, MapPosition pos); -} diff --git a/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java b/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java index 2fb2902f..37991c5e 100644 --- a/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java +++ b/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java @@ -18,15 +18,15 @@ package org.oscim.layers.marker; import java.util.List; import org.oscim.core.BoundingBox; -import org.oscim.core.MapPosition; import org.oscim.core.Point; +import org.oscim.event.Gesture; +import org.oscim.event.GestureListener; import org.oscim.event.MotionEvent; -import org.oscim.event.TouchListener; import org.oscim.map.Map; import org.oscim.map.Viewport; public class ItemizedIconLayer extends ItemizedLayer - implements TouchListener { + implements GestureListener { //static final Logger log = LoggerFactory.getLogger(ItemizedIconOverlay.class); protected final List mItemList; @@ -108,13 +108,13 @@ public class ItemizedIconLayer extends ItemizedLayer extends ItemizedLayer that = ItemizedIconLayer.this; + // if (that.mOnItemGestureListener == null) { + // return false; + // } + // return onLongPressHelper(index, getItem(index)); + // } + // }; - private final ActiveItem mActiveItemLongPress = new ActiveItem() { - @Override - public boolean run(final int index) { - final ItemizedIconLayer that = ItemizedIconLayer.this; - if (that.mOnItemGestureListener == null) { - return false; - } - return onLongPressHelper(index, getItem(index)); - } - }; - - @Override - public boolean onPress(MotionEvent e, MapPosition pos) { - return false; - } + // @Override + // public boolean onPress(MotionEvent e, MapPosition pos) { + // return false; + // } /** * When a content sensitive action is performed the content item needs to be @@ -164,7 +164,7 @@ public class ItemizedIconLayer extends ItemizedLayer extends ItemizedLayer { + + private final CopyOnWriteArrayList mLayerList; + private final Map mMap; + + private boolean mDirtyLayers; + private LayerRenderer[] mLayerRenderer; + private Layer[] mLayers; + + Layers(Map map) { + mMap = map; + mLayerList = new CopyOnWriteArrayList(); + } + + @Override + public synchronized Layer get(int index) { + return mLayerList.get(index); + } + + @Override + public synchronized int size() { + return mLayerList.size(); + } + + @Override + public synchronized void add(int index, Layer layer) { + if (mLayerList.contains(layer)) + throw new IllegalArgumentException("layer added twice"); + + if (layer instanceof UpdateListener) + mMap.bind((UpdateListener) layer); + //if (layer instanceof InputListener) + // mMap.bind((InputListener) layer); + + mLayerList.add(index, layer); + mDirtyLayers = true; + } + + @Override + public synchronized Layer remove(int index) { + mDirtyLayers = true; + + Layer remove = mLayerList.remove(index); + + if (remove instanceof UpdateListener) + mMap.unbind((UpdateListener) remove); + //if (remove instanceof InputListener) + // mMap.unbind((InputListener) remove); + + return remove; + } + + @Override + public synchronized Layer set(int index, Layer layer) { + if (mLayerList.contains(layer)) + throw new IllegalArgumentException("layer added twice"); + + mDirtyLayers = true; + Layer remove = mLayerList.set(index, layer); + + // unbind replaced layer + if (remove instanceof UpdateListener) + mMap.unbind((UpdateListener) remove); + //if (remove instanceof InputListener) + // mMap.unbind((InputListener) remove); + + return remove; + } + + /** + * Should only be used by MapRenderer. + * + * @return the current LayerRenderer as array. + * */ + public LayerRenderer[] getLayerRenderer() { + if (mDirtyLayers) + updateLayers(); + + return mLayerRenderer; + } + + void destroy() { + if (mDirtyLayers) + updateLayers(); + + for (Layer o : mLayers) + o.onDetach(); + + // TODO need to clear lists here? + } + + boolean handleGesture(Gesture g, MotionEvent e) { + if (mDirtyLayers) + updateLayers(); + + for (Layer o : mLayers) + if (o instanceof GestureListener) + if (((GestureListener) o).onGesture(g, e)) + return true; + + return false; + } + + void handleMotionEvent(MotionEvent e) { + if (mDirtyLayers) + updateLayers(); + + for (Layer o : mLayers) + if (o instanceof InputListener) + ((InputListener) o).onMotionEvent(e); + } + + private synchronized void updateLayers() { + mLayers = new Layer[mLayerList.size()]; + int numRenderLayers = 0; + + for (int i = 0, n = mLayerList.size(); i < n; i++) { + Layer o = mLayerList.get(i); + + if (o.getRenderer() != null) + numRenderLayers++; + mLayers[i] = o; + } + + mLayerRenderer = new LayerRenderer[numRenderLayers]; + + for (int i = 0, cnt = 0, n = mLayerList.size(); i < n; i++) { + Layer o = mLayerList.get(i); + LayerRenderer l = o.getRenderer(); + if (l != null) + mLayerRenderer[cnt++] = l; + } + + mDirtyLayers = false; + } +} diff --git a/vtm/src/org/oscim/map/Map.java b/vtm/src/org/oscim/map/Map.java index 677a52a0..d630be68 100644 --- a/vtm/src/org/oscim/map/Map.java +++ b/vtm/src/org/oscim/map/Map.java @@ -14,19 +14,16 @@ */ package org.oscim.map; -import java.util.AbstractList; import java.util.LinkedHashSet; import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import org.oscim.core.BoundingBox; import org.oscim.core.MapPosition; +import org.oscim.event.Gesture; +import org.oscim.event.GestureDetector; import org.oscim.event.MotionEvent; -import org.oscim.layers.Layer; import org.oscim.layers.MapEventLayer; import org.oscim.layers.tile.bitmap.BitmapTileLayer; import org.oscim.layers.tile.vector.VectorTileLayer; -import org.oscim.renderer.LayerRenderer; import org.oscim.renderer.MapRenderer; import org.oscim.theme.IRenderTheme; import org.oscim.theme.InternalRenderTheme; @@ -71,10 +68,11 @@ public abstract class Map { protected boolean mClearMap; protected final MapEventLayer mEventLayer; + protected GestureDetector mGestureDetector; private VectorTileLayer mBaseLayer; - private Set mMotionListeners = new LinkedHashSet(); + private Set mInputListeners = new LinkedHashSet(); private Set mUpdateListeners = new LinkedHashSet(); public Map() { @@ -83,11 +81,13 @@ public abstract class Map { mAnimator = new MapAnimator(this, mViewport); mMapPosition = new MapPosition(); - mLayers = new Layers(); + mLayers = new Layers(this); mAsyncExecutor = new AsyncExecutor(2); mEventLayer = new MapEventLayer(this); mLayers.add(0, mEventLayer); + + //mGestureDetector = new GestureDetector(this, mLayers); } public MapEventLayer getEventLayer() { @@ -254,27 +254,24 @@ public abstract class Map { } /** - * @return estimated visible axis aligned bounding box + * @return MapAnimator instance */ - public BoundingBox getBoundingBox() { - return mViewport.getViewBox(); - } - public MapAnimator getAnimator() { return mAnimator; } + /** + * Register InputListener + */ public void bind(InputListener listener) { - mMotionListeners.add(listener); + mInputListeners.add(listener); } + /** + * Unregister InputListener + */ public void unbind(InputListener listener) { - mMotionListeners.remove(listener); - } - - public void handleMotionEvent(MotionEvent e) { - for (InputListener l : mMotionListeners) - l.onMotionEvent(e); + mInputListeners.remove(listener); } /** @@ -291,115 +288,16 @@ public abstract class Map { mUpdateListeners.remove(l); } - public final class Layers extends AbstractList { + // TODO make protected + public void handleMotionEvent(MotionEvent e) { + mLayers.handleMotionEvent(e); - private final CopyOnWriteArrayList mLayerList; - - Layers() { - mLayerList = new CopyOnWriteArrayList(); - } - - @Override - public synchronized Layer get(int index) { - return mLayerList.get(index); - } - - @Override - public synchronized int size() { - return mLayerList.size(); - } - - @Override - public synchronized void add(int index, Layer layer) { - if (mLayerList.contains(layer)) - throw new IllegalArgumentException("layer added twice"); - - if (layer instanceof UpdateListener) - bind((UpdateListener) layer); - if (layer instanceof InputListener) - bind((InputListener) layer); - - mLayerList.add(index, layer); - mDirtyLayers = true; - } - - @Override - public synchronized Layer remove(int index) { - mDirtyLayers = true; - - Layer remove = mLayerList.remove(index); - - if (remove instanceof UpdateListener) - unbind((UpdateListener) remove); - if (remove instanceof InputListener) - unbind((InputListener) remove); - - return remove; - } - - @Override - public synchronized Layer set(int index, Layer layer) { - if (mLayerList.contains(layer)) - throw new IllegalArgumentException("layer added twice"); - - mDirtyLayers = true; - Layer remove = mLayerList.set(index, layer); - - // unbind replaced layer - if (remove instanceof UpdateListener) - unbind((UpdateListener) remove); - if (remove instanceof InputListener) - unbind((InputListener) remove); - - return remove; - } - - private boolean mDirtyLayers; - private LayerRenderer[] mLayerRenderer; - - public LayerRenderer[] getLayerRenderer() { - if (mDirtyLayers) - updateLayers(); - - return mLayerRenderer; - } - - public void destroy() { - if (mDirtyLayers) - updateLayers(); - - for (Layer o : mLayers) - o.onDetach(); - } - - Layer[] mLayers; - - private synchronized void updateLayers() { - if (!mDirtyLayers) - return; - - mLayers = new Layer[mLayerList.size()]; - - int numRenderLayers = 0; - - for (int i = 0, n = mLayerList.size(); i < n; i++) { - Layer o = mLayerList.get(i); - - if (o.getRenderer() != null) - numRenderLayers++; - mLayers[i] = o; - } - - mLayerRenderer = new LayerRenderer[numRenderLayers]; - - for (int i = 0, cntR = 0, n = mLayerList.size(); i < n; i++) { - Layer o = mLayerList.get(i); - LayerRenderer l = o.getRenderer(); - if (l != null) - mLayerRenderer[cntR++] = l; - } - - mDirtyLayers = false; - } + for (InputListener l : mInputListeners) + l.onMotionEvent(e); } + + public boolean handleGesture(Gesture g, MotionEvent e) { + return mLayers.handleGesture(g, e); + } + }