Google Maps Android, detect user interation in a location aware map - android

I've an Android Application that displays a map.
The map is by default centered on the user position following its movements (the center is updated according to the position updates).
However I want the user to be able to use gestures to navigate throw the map. When the user starts the navigation I want the "following" to stop, and a button is displayed so that it can start again.
How can I know when the user has moved the map center?
On the GoogleMap.OnCameraChangeListener, I don't know if the change is due to a location changed or a user interaction.
I've a a kind of working solution using the OnCameraChangeListener, but its a bit "dirty" and I don't find it very nice:
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
// gpsPosition contains the last position obtained
if(isFollowing && gpsPosition!=null && (Math.abs(position.target.latitude - gpsPosition.latitude)>0.000001 || Math.abs(position.target.longitude - gpsPosition.longitude)>0.000001) ){
isFollowing = false;
map.getUiSettings().setMyLocationButtonEnabled(true);
}
}
});
Is there a nice Solution to the problem?

If you want to detect user interacting with the map OnCameraChangeListener is surely too late to do that. It may be called seconds after user started interacting with the map.
What you need to do, is add 2 other listeners:
OnMyLocationButtonClickListener - to know when to start tracking (set your isFollowing to true)
OnMyLocationChangeListener - when your isFollowing is true, call GoogleMap.animateCamera to the new position
So the only thing left is when you set isFollowing to false:
Create an invisible overlay in your layout (like here, but with normal View instead of custom) and assign this overlay (the View) an OnTouchListener (ok, it is 3rd, I lied that you need only 2).
In this listener always return false, but also set isFollowing to false. This when when user starts interacting with the map and you should stop automated camera movements.
I also see you are showing and hiding my location button, so do this where you change the value of isFollowing. Btw.: good idea to do that. I'll try it too.

I have never tried this, but here are some ideas off the top of my (balding) head:
Option #1: Handle the location change updates yourself, by recentering the map yourself when the location change comes in. If a camera-change event occurs that does not appear to be tied to the location change (e.g., 5+ milliseconds later), that was presumably a user modifying the map via gestures.
Option #2: Subclass the Maps V2 MapView and override touch-related methods like onTouchEvent(). In addition to chaining to the superclass, you would know that a camera change that happens very soon from now probably is from the user modifying the map via gestures.
Option #3: Do both of the above. That way, all changes to the map should touch your code, which should increase the reliability of your determining the source of the camera change.
BTW, I filed a feature request for a better solution.

The simpliest way should be saving the center Longitude and Latitude of your Map.
Now create an EventListener and check it in an if-statement:
if(oldLongLat != newLongLat){
//Something has moved
}else {
//Nothing has moved
}

Related

How to click a GoogleMap Polygon below a Marker?

My android application uses a GoogleMap with Polygons to show areas of interest to the user. If the user clicks on a Polygon the click event is handled by the OnPolygonClickListener and an info dialog is displayed. My problem lies with a Marker showing the current location of the user which is blocking click events passing to the Polygon below.
Sidenote: The click area of the marker also seems to be larger than visible. This circle actually blocks nearly the whole square below it.
What I tried so far:
Catching the click event and ignore it
getMap().setOnMarkerClickListener(new OnMarkerClickListener() {
public boolean onMarkerClick(Marker marker) {
return true;
}
});
Does not work in this case because we can either return false which triggers the default behavior (zooming in on the marker) or true which ignores the click event altogether since - per documentation - there is only one event per click.
Changing the z-index of the polygon: Does not work because - again per documentation - "Markers are effectively considered to be in a separate z-index group compared to other overlays or shapes, regardless of the z-index of the other overlays" so click events are always passed to potential markers first.
So is there a way to make a Polygon below a Marker clickable or at least reduce the clickable marker area to the visible size?
Following the suggestion of MrUpsidown and using googlemaps PolyUtil I implemented a manual validation to trigger a Polygon targeted click manually. I can not tell about efficiency of containsLocation() when handling a larger amount of polygons but it works for the time being.
mMap.setOnMarkerClickListener { marker ->
if (marker == userLocation) {
mPolygons.forEach { polygon ->
if(PolyUtil.containsLocation(userLocation?.position, polygon.points, false)) {
// Do Polygon stuff here
return#forEach
}
}
true
} else {
// Do optional marker stuff here
true
}
}

Do some actions only when current location is acquired

I am designing the map-based app and I want to do certain set of steps when user's location has been acquired (blue dot has been shown on the map). On some of the devices it may take up to 1 min due to location services ramp-up process.. So the question is how do I know when Android Map managed to acquire my location, so I can start doing the rest of initialisation process?
I was thinking about implementing my own LocationListener and trigger callback when I receive location, but this solution seems worthless since I need to only know the point when current location has been acquired and blue dot has been shown. I don't care about location updates.
If you are talking about the blue dot on a map, I suppose you add the MyLocationOverlay to your map. If it is so, there is an easy way to do what you want - MyLocationOverlay has runOnFirstFix(Rannable) interface, so provided Runnable will be run as soon as the map acquires your current location:
this.currentLocationOverlay.runOnFirstFix(new Runnable() {
#Override
public void run() {
//do your magic here
}
});

Android Maps API v2 - approaches to marker rotation?

We're porting an app from the v1 Maps API to the v2 API, and having trouble with markers.
We need markers that point in a specific geographic direction. In V1, we could build the bitmap pointing in the right direction at draw time, but in V2 the marker bitmap can't be changed.
I'm not sure if the best approach is to destroy and re-build all our markers when the map is rotated (which sounds like a performance problem), or to try drawing them all ourselves. That could be via a TileOverlay or via a view of our own that we sat on top of the map.
I don't really like any of these approaches. Has anyone tried any of them ?
UPDATE:
I've tried drawing via a view of our own, but that was far too laggy when the map was dragged.
I'm now destroying & recreating the markers, but that is (as expected) a performance problem, taking ~2000mS to update 60 markers.
Good news everyone! Google has added rotation to the Maps API, so we don't have to roll our own implementations anymore.
They have also added flat markers, which I guess is more related to the original question. A flattened marker will always stay in the orientation it was originally drawn on the map: https://developers.google.com/maps/documentation/android/marker#flatten_a_marker
The only requirement is that you reference the latest version of Google Play Services.
I'm also rewriting my app (Runbot) for the new API and had to figure out how to create custom markers representing milestones (like 1km, 2km, ...) and how to show or show not all of them depending on the zoom level.
I had a custom drawable that I used for the v1 API and what I do now to render the markers is about this (Position is a class of my own that holds the position and further information; all needed here is its LatLng property):
private void addMarker(Position p, MilestoneDrawable milestone) {
if (mMarkers.containsKey(p)) {
mMarkers.get(p).setVisible(true);
} else {
Marker m = mMap.addMarker(new MarkerOptions()
.position(p.latLng)
.icon(BitmapDescriptorFactory.fromBitmap(Util.drawableToBitmap(milestone)))
.anchor(0.5f, 1.0f) // bottom center
);
mMarkers.put(p, m);
}
}
Besides creating and adding the custom markers, what you see is that I keep the markers in a HashMap so I do not have to destroy and create them all the time. When it comes to zooming and deciding which ones to show, I first set all of the markers to invisible and than call addMarker() for those I want to be shown, and those which I already have in the HashMap I simply make visible again.
I hope this helps you a bit. I have a bit of mixed feelings towards the new API...
I had a similar problem where I had markers that needed to rotate. My solution was to have the object the marker represented be responsible for generating the marker. I have a few methods in the object that look like:
protected Marker getMarker(GoogleMap map) {
if (this.marker == null) {
marker = map.addMarker(new MarkerOptions().position(location).
icon(BitmapDescriptorFactory.fromBitmap(BusMarkerImageFactory.
getMarkerIcon(heading))));
}
return marker;
}
protected void updateMarker(GoogleMap map) {
if (marker != null) {
rotateIcon();
marker.setPosition(location);
} else {
getMarker(map);
}
private void rotateIcon() {
marker.setIcon(BitmapDescriptorFactory.
fromBitmap(BusMarkerImageFactory.getMarkerIcon(heading)));
}
This is from a system that draws buses with the markers pointing in the direction they are heading, so of course, your code will be different, but the concept is very similar. Instead of rebuilding the entire marker you're keeping a reference to it somewhere and then simply resetting the icon.
Of course, drawing all those bitmaps for minor changes is a drain on memory. I used a flyweight pattern in the (incorrectly named) BusMarkerImageFactory to keep 16 images for 16 possible heading ranges. It is a static class that simply takes in the heading and returns the image that I've mapped to that range.
can't you use addMarker(new MarkerOptions()) method ?
If you need a custom marker you can create an implementation of InfoWindowAdapter and use that implementation like mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
here is the documentation for InfoWindowAdapter

Android How to stop animateTo in MapController

In order to animate to some point on map i use
myMapController.animateTo(point);
But sometimes I have to run another animation to point. In order not to have some elements flashing I need to stop last animation.
I tried this two:
myMapController.stopPanning()
myMapController.stopAnimation(false);
but it didn't help
Any ideas?
Thanks!
To stop animation you need to call the method bellow with parameter true (not false as in your question):
myMapController.stopAnimation(true);
This stops the animation and moves map instantanely to the final location requested by animateTo().
If you want to stop animation and keep the map in the position where it was (in the midle of animation), use the following:
GeoPoint movingCenter = mapView.getProjection().fromPixels(mapView.getWidth()/2, mapView.getHeight()/2);
myMapController.stopAnimation(true);
myMapController.setCenter(movingCenter);
Regards.

Replicating Maps Follow Current Location Feature in Android

I am looking to figure out how I can replicate the follow current location feature that exists in Maps on Android (and iPhone) using the Google Maps external library. The way the feature works in Maps is that when you press the button it begins panning with your location until you touch (and/or move) the map. Using the following I have been able to get my app to keep the current location on the screen but not keep the current location in the center of the screen (it pans when the location gets near the edge of the screen whereas Maps keeps it in the center the whole time):
butMapCurrentLocation.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
runOnUiThread(new Runnable() {
public void run() {
mapController.animateTo(myLocationOverlay.getMyLocation());
}
});
}
});
I also tried setCenter rather than animateTo and they seem to have the same effect.
Additionally it is worth noting I need to be able to stop this function that keeps the location in the center (such as when another button is pressed or the map is touched). The Runnable above doesn't stop right away and I cannot get it to stop properly when requested even when using .cancel as discussed here: Android: How do I stop Runnable?
There is no official way yet to do that in Google Maps v2.
You can do what you want however. There is two things you have to do:
Register for location updates from some LocationProviders (eg gps) to get the user's location. When you get the location animate map to the new location.
Stop this when the user moved the map.
So doing 1. is easy, doing 2. is not possible officially with the Maps v2 api. Look at this issue for more details.
The workaround suggested there is to create your own MapView (extend the original MapView) and override it's dispatchTouchEvent method. There you can catch any touch events and can detect if the user touched/moved the map.

Categories

Resources