From 5b9503275e2aa47f7bbcc10de6af4b7495aaca5e Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Sat, 21 Feb 2015 20:16:13 +0100 Subject: [PATCH] Add marker onclick support, optimize drawing --- src/org/microg/gms/maps/BackendMap.java | 40 +++++++-- src/org/microg/gms/maps/GoogleMapImpl.java | 21 ++++- .../bitmap/BitmapDescriptorFactoryImpl.java | 2 +- .../gms/maps/bitmap/BitmapDescriptorImpl.java | 22 ++++- .../maps/bitmap/DefaultBitmapDescriptor.java | 87 +++++++++++++++---- .../microg/gms/maps/markup/CircleImpl.java | 14 +++ .../microg/gms/maps/markup/MarkerImpl.java | 54 +++++++++--- src/org/microg/gms/maps/markup/Markup.java | 6 ++ 8 files changed, 205 insertions(+), 41 deletions(-) diff --git a/src/org/microg/gms/maps/BackendMap.java b/src/org/microg/gms/maps/BackendMap.java index 77c8afab..34b91a59 100644 --- a/src/org/microg/gms/maps/BackendMap.java +++ b/src/org/microg/gms/maps/BackendMap.java @@ -41,7 +41,9 @@ import org.oscim.map.Viewport; import org.oscim.theme.VtmThemes; import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; -public class BackendMap { +import java.util.HashMap; + +public class BackendMap implements ItemizedLayer.OnItemGestureListener { private final static String TAG = "GmsBackendMap"; private final Context context; @@ -51,6 +53,8 @@ public class BackendMap { private final OSciMap4TileSource tileSource; private final TileCache cache; private final ItemizedLayer items; + private java.util.Map markupMap = new HashMap<>(); + private boolean redrawPosted = false; public BackendMap(Context context) { this.context = context; @@ -65,7 +69,8 @@ public class BackendMap { layers.add(buildings = new BuildingLayer(mapView.map(), baseLayer)); layers.add(new LabelLayer(mapView.map(), baseLayer)); layers.add(items = new ItemizedLayer<>(mapView.map(), new MarkerSymbol(new AndroidBitmap(BitmapFactory - .decodeResource(ResourcesContainer.get(), R.drawable.maps_default_marker)), 0.5F, 1))); + .decodeResource(ResourcesContainer.get(), R.drawable.nop)), 0.5F, 1))); + items.setOnItemGestureListener(this); mapView.map().setTheme(VtmThemes.DEFAULT); } @@ -138,9 +143,10 @@ public class BackendMap { mapView.map().animator().cancel(); } - public T add(T markup) { + public synchronized T add(T markup) { switch (markup.getType()) { case MARKER: + markupMap.put(markup.getId(), markup); items.addItem(markup.getMarkerItem(context)); redraw(); break; @@ -157,14 +163,16 @@ public class BackendMap { return markup; } - public void clear() { + public synchronized void clear() { + markupMap.clear(); items.removeAllItems(); redraw(); } - public void remove(Markup markup) { + public synchronized void remove(Markup markup) { switch (markup.getType()) { case MARKER: + markupMap.remove(markup.getId()); items.removeItem(items.getByUid(markup.getId())); redraw(); break; @@ -173,7 +181,11 @@ public class BackendMap { } } - public void update(Markup markup) { + public synchronized void update(Markup markup) { + if (markup == null || !markup.isValid()) { + Log.d(TAG, "Tried to update() invalid markup!"); + return; + } switch (markup.getType()) { case MARKER: // TODO: keep order @@ -188,4 +200,20 @@ public class BackendMap { Log.d(TAG, "Unknown markup: " + markup); } } + + @Override + public boolean onItemSingleTapUp(int index, MarkerItem item) { + Markup markup = markupMap.get(item.getUid()); + if (markup != null) { + if (markup.onClick()) return true; + } + return false; + } + + @Override + public boolean onItemLongPress(int index, MarkerItem item) { + // TODO: start drag+drop + Log.d(TAG, "onItemLongPress: " + markupMap.get(item.getUid())); + return false; + } } diff --git a/src/org/microg/gms/maps/GoogleMapImpl.java b/src/org/microg/gms/maps/GoogleMapImpl.java index 6d7961ac..9d5d0be2 100644 --- a/src/org/microg/gms/maps/GoogleMapImpl.java +++ b/src/org/microg/gms/maps/GoogleMapImpl.java @@ -80,6 +80,7 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub private int markerCounter = 0; private int circleCounter = 0; + private IOnMarkerClickListener onMarkerClickListener; public GoogleMapImpl(LayoutInflater inflater, GoogleMapOptions options) { context = inflater.getContext(); @@ -212,6 +213,8 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub @Override public void clear() throws RemoteException { backendMap.clear(); + circleCounter = 0; + markerCounter = 0; } @Override @@ -223,6 +226,22 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub public void remove(Markup markup) { backendMap.remove(markup); } + + @Override + public boolean onClick(Markup markup) { + if (markup instanceof IMarkerDelegate) { + if (onMarkerClickListener != null) { + try { + if (onMarkerClickListener.onMarkerClick((IMarkerDelegate) markup)) + return true; + } catch (RemoteException e) { + Log.w(TAG, e); + } + } + // TODO: open InfoWindow + } + return false; + } /* Map options @@ -321,7 +340,7 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub @Override public void setOnMarkerClickListener(IOnMarkerClickListener listener) throws RemoteException { - + this.onMarkerClickListener = listener; } @Override diff --git a/src/org/microg/gms/maps/bitmap/BitmapDescriptorFactoryImpl.java b/src/org/microg/gms/maps/bitmap/BitmapDescriptorFactoryImpl.java index 41ed4ac8..5b3ecbe8 100644 --- a/src/org/microg/gms/maps/bitmap/BitmapDescriptorFactoryImpl.java +++ b/src/org/microg/gms/maps/bitmap/BitmapDescriptorFactoryImpl.java @@ -41,7 +41,7 @@ public class BitmapDescriptorFactoryImpl extends IBitmapDescriptorFactoryDelegat @Override public IObjectWrapper defaultMarker() throws RemoteException { - return ObjectWrapper.wrap(new DefaultBitmapDescriptor()); + return ObjectWrapper.wrap(DefaultBitmapDescriptor.DEFAULT_DESCRIPTOR); } @Override diff --git a/src/org/microg/gms/maps/bitmap/BitmapDescriptorImpl.java b/src/org/microg/gms/maps/bitmap/BitmapDescriptorImpl.java index 955e5e88..4acfac09 100644 --- a/src/org/microg/gms/maps/bitmap/BitmapDescriptorImpl.java +++ b/src/org/microg/gms/maps/bitmap/BitmapDescriptorImpl.java @@ -19,13 +19,18 @@ package org.microg.gms.maps.bitmap; import android.content.Context; import android.graphics.Bitmap; import android.util.Log; + import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.maps.model.BitmapDescriptor; +import java.util.HashSet; +import java.util.Set; + public class BitmapDescriptorImpl { private BitmapDescriptor descriptor; private boolean loadStarted = false; + private Set waitingForLoad = new HashSet<>(); public BitmapDescriptorImpl(IObjectWrapper remoteObject) { this(new BitmapDescriptor(remoteObject)); @@ -57,9 +62,13 @@ public class BitmapDescriptorImpl { return null; } - public synchronized void loadBitmapAsync(final Context context, final Runnable after) { + public synchronized boolean loadBitmapAsync(final Context context, Runnable after) { + if (getBitmap() != null) { + return false; + } + waitingForLoad.add(after); if (loadStarted) - return; + return true; loadStarted = true; if (getDescriptor() != null) { new Thread(new Runnable() { @@ -67,11 +76,18 @@ public class BitmapDescriptorImpl { public void run() { Log.d("BitmapDescriptor", "Start loading " + getDescriptor()); if (getDescriptor().loadBitmap(context) != null) { - after.run(); + Set waitingForLoad; + synchronized (BitmapDescriptorImpl.this) { + waitingForLoad = BitmapDescriptorImpl.this.waitingForLoad; + } + for (Runnable after : waitingForLoad) { + after.run(); + } } Log.d("BitmapDescriptor", "Done loading " + getDescriptor()); } }).start(); } + return true; } } diff --git a/src/org/microg/gms/maps/bitmap/DefaultBitmapDescriptor.java b/src/org/microg/gms/maps/bitmap/DefaultBitmapDescriptor.java index c7539565..a8eac2f4 100644 --- a/src/org/microg/gms/maps/bitmap/DefaultBitmapDescriptor.java +++ b/src/org/microg/gms/maps/bitmap/DefaultBitmapDescriptor.java @@ -19,36 +19,87 @@ package org.microg.gms.maps.bitmap; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; + import com.google.android.gms.R; +import com.google.android.gms.dynamic.ObjectWrapper; + import org.microg.gms.maps.ResourcesContainer; public class DefaultBitmapDescriptor extends AbstractBitmapDescriptor { - private float hue; + public static final DefaultBitmapDescriptor DEFAULT_DESCRIPTOR = new DefaultBitmapDescriptor(0); + public static final BitmapDescriptorImpl DEFAULT_DESCRIPTOR_IMPL = new BitmapDescriptorImpl(ObjectWrapper.wrap(DEFAULT_DESCRIPTOR)); + public static final int DEGREES = 360; - public DefaultBitmapDescriptor() { - this(0); - } + private final float hue; public DefaultBitmapDescriptor(float hue) { - this.hue = hue; + this.hue = hue > 180 ? -DEGREES + hue : hue; } @Override public Bitmap generateBitmap(Context context) { - Bitmap source = BitmapFactory - .decodeResource(ResourcesContainer.get(), R.drawable.maps_default_marker); - Bitmap bitmap = Bitmap - .createBitmap(source.getWidth(), source.getHeight(), source.getConfig()); - float[] hsv = new float[3]; - for (int x = 0; x < bitmap.getWidth(); x++) { - for (int y = 0; y < bitmap.getHeight(); y++) { - int pixel = source.getPixel(x, y); - Color.colorToHSV(pixel, hsv); - hsv[0] = (hsv[0] + hue) % 360; - bitmap.setPixel(x, y, Color.HSVToColor(Color.alpha(pixel), hsv)); - } + Bitmap source; + if (this == DEFAULT_DESCRIPTOR) { + source = BitmapFactory.decodeResource(ResourcesContainer.get(), R.drawable.maps_default_marker); + } else { + source = DEFAULT_DESCRIPTOR.loadBitmap(context); } + if (hue % DEGREES == 0) return source; + Paint paint = new Paint(); + paint.setColorFilter(adjustHue(hue)); + Bitmap bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), source.getConfig()); + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(source, 0, 0, paint); return bitmap; } + + /** + * Creates a HUE ajustment ColorFilter + *

+ * see http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953 + * see http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html + * + * @param value degrees to shift the hue. + */ + public static ColorFilter adjustHue(float value) { + ColorMatrix cm = new ColorMatrix(); + adjustHue(cm, value); + return new ColorMatrixColorFilter(cm); + } + + /** + * see http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953 + * see http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html + */ + public static void adjustHue(ColorMatrix cm, float value) { + value = cleanValue(value, 180f) / 180f * (float) Math.PI; + if (value == 0) { + return; + } + float cosVal = (float) Math.cos(value); + float sinVal = (float) Math.sin(value); + float lumR = 0.213f; + float lumG = 0.715f; + float lumB = 0.072f; + float[] mat = new float[]{lumR + cosVal * (1 - lumR) + sinVal * (-lumR), + lumG + cosVal * (-lumG) + sinVal * (-lumG), + lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, + lumR + cosVal * (-lumR) + sinVal * (0.143f), + lumG + cosVal * (1 - lumG) + sinVal * (0.140f), + lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, + lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), + lumG + cosVal * (-lumG) + sinVal * (lumG), + lumB + cosVal * (1 - lumB) + sinVal * (lumB), + 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f}; + cm.postConcat(new ColorMatrix(mat)); + } + + protected static float cleanValue(float p_val, float p_limit) { + return Math.min(p_limit, Math.max(-p_limit, p_val)); + } } diff --git a/src/org/microg/gms/maps/markup/CircleImpl.java b/src/org/microg/gms/maps/markup/CircleImpl.java index 39b11be5..dd98ac92 100644 --- a/src/org/microg/gms/maps/markup/CircleImpl.java +++ b/src/org/microg/gms/maps/markup/CircleImpl.java @@ -50,6 +50,7 @@ public class CircleImpl extends ICircleDelegate.Stub implements Markup { private CircleLayer layer; private Point point; private float drawRadius; + private boolean removed = false; public CircleImpl(String id, CircleOptions options, MarkupListener listener) { this.id = id; @@ -65,6 +66,9 @@ public class CircleImpl extends ICircleDelegate.Stub implements Markup { @Override public void remove() throws RemoteException { listener.remove(this); + removed = true; + layer.setEnabled(false); + layer = null; } @Override @@ -158,6 +162,16 @@ public class CircleImpl extends ICircleDelegate.Stub implements Markup { return id.hashCode(); } + @Override + public boolean onClick() { + return false; + } + + @Override + public boolean isValid() { + return !removed; + } + @Override public MarkerItem getMarkerItem(Context context) { return null; diff --git a/src/org/microg/gms/maps/markup/MarkerImpl.java b/src/org/microg/gms/maps/markup/MarkerImpl.java index cd61e9f8..045f04fd 100644 --- a/src/org/microg/gms/maps/markup/MarkerImpl.java +++ b/src/org/microg/gms/maps/markup/MarkerImpl.java @@ -20,12 +20,15 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.RemoteException; import android.util.Log; + import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.internal.IMarkerDelegate; + import org.microg.gms.maps.GmsMapsTypeHelper; import org.microg.gms.maps.bitmap.BitmapDescriptorImpl; +import org.microg.gms.maps.bitmap.DefaultBitmapDescriptor; import org.oscim.android.canvas.AndroidBitmap; import org.oscim.layers.Layer; import org.oscim.layers.marker.MarkerItem; @@ -39,6 +42,8 @@ public class MarkerImpl extends IMarkerDelegate.Stub implements Markup { private final MarkerOptions options; private final MarkupListener listener; private BitmapDescriptorImpl icon; + private AndroidBitmap oldBitmap; + private boolean removed = false; public MarkerImpl(String id, MarkerOptions options, MarkupListener listener) { this.id = id; @@ -55,6 +60,9 @@ public class MarkerImpl extends IMarkerDelegate.Stub implements Markup { @Override public void remove() { listener.remove(this); + removed = true; + icon = null; + oldBitmap = null; } @Override @@ -200,27 +208,49 @@ public class MarkerImpl extends IMarkerDelegate.Stub implements Markup { return bitmap.getHeight(); } + @Override + public boolean onClick() { + return listener.onClick(this); + } + + @Override + public boolean isValid() { + return !removed; + } + @Override public MarkerItem getMarkerItem(Context context) { MarkerItem item = new MarkerItem(getId(), getTitle(), getSnippet(), GmsMapsTypeHelper.fromLatLng(getPosition())); - if (icon != null) { - if (icon.getBitmap() != null) { - item.setMarker( - new MarkerSymbol(new AndroidBitmap(icon.getBitmap()), options.getAnchorU(), - options.getAnchorV(), !options.isFlat())); - } else { - icon.loadBitmapAsync(context, new Runnable() { - @Override - public void run() { - listener.update(MarkerImpl.this); - } - }); + BitmapDescriptorImpl icon = this.icon; + if (icon == null) + icon = DefaultBitmapDescriptor.DEFAULT_DESCRIPTOR_IMPL; + if (icon.getBitmap() != null) { + oldBitmap = new AndroidBitmap(icon.getBitmap()); + prepareMarkerIcon(item); + } else { + if (!icon.loadBitmapAsync(context, new Runnable() { + @Override + public void run() { + listener.update(MarkerImpl.this); + } + })) { + // Was loaded since last check... + oldBitmap = new AndroidBitmap(icon.getBitmap()); + prepareMarkerIcon(item); + } + // Keep old icon while loading new + if (oldBitmap != null) { + prepareMarkerIcon(item); } } return item; } + private void prepareMarkerIcon(MarkerItem item) { + item.setMarker(new MarkerSymbol(oldBitmap, options.getAnchorU(), options.getAnchorV(), !options.isFlat())); + } + @Override public Layer getLayer(Context context, Map map) { return null; diff --git a/src/org/microg/gms/maps/markup/Markup.java b/src/org/microg/gms/maps/markup/Markup.java index 9f30b1e5..787fca4a 100644 --- a/src/org/microg/gms/maps/markup/Markup.java +++ b/src/org/microg/gms/maps/markup/Markup.java @@ -31,6 +31,10 @@ public interface Markup { public String getId(); + public boolean onClick(); + + public boolean isValid(); + public static enum Type { MARKER, LAYER } @@ -39,5 +43,7 @@ public interface Markup { void update(Markup markup); void remove(Markup markup); + + boolean onClick(Markup markup); } }