I am using Google Maps and its clustering utility in my app. Clustering itself is working fine, but I have issue, when I try to deal with configuration changes.
This is my case:
User sees a map on a screen, which is a fragment.
When clustering is completed, markers on map appear. User can interact with markers.
When user selects a marker, it is highlighted and BottomSheet is expanded to display.
If user rotates screen (i.e. configuration change happens), I save what marker was selected using onSaveInstanceState (actually I do not save marker itself, but only a link to related List entry like ID). Then, I want to restore that user selection that was before configuration change.
Clustering itself is executed like this:
clusterManager.clearItems();
clusterManager.addItems(eventManager.getEventList());
clusterManager.cluster();
This code is executed, when data is received from server. When clustering is executed, all markers, obviously, are recreated as well. So in order to highlight previous user selection (previous marker), I must know WHEN clustering utility finishes its operation. By that time I am sure that I can safely use such functions like:
clusterManager.getRenderer()).getMarker(<param>)
and
clusterManager.getRenderer()).getClusterItem(<param>)
otherwise, these will return null sometimes, if clustering task is not completed yet.
However, I cannot find a reasonable way, how to get a response from clustering utility (i.e. ClusterManager), when clustering is completed. I think I need to update this standard clustering code:
/**
* Runs the clustering algorithm in a background thread, then re-paints when results come back.
*/
private class ClusterTask extends AsyncTask<Float, Void, Set<? extends Cluster<T>>> {
#Override
protected Set<? extends Cluster<T>> doInBackground(Float... zoom) {
mAlgorithmLock.readLock().lock();
try {
return mAlgorithm.getClusters(zoom[0]);
} finally {
mAlgorithmLock.readLock().unlock();
}
}
#Override
protected void onPostExecute(Set<? extends Cluster<T>> clusters) {
mRenderer.onClustersChanged(clusters);
}
}
I think, that onPostExecute should provide a response not to Renderer only, but to a user of ClusterManager (like my fragment) that clustering is done. But I do not want to modify standard code.
Is there a better way how to handle this case?
You are going about this the wrong way.
You should implement:
class YourClusterItemRenderer extends DefaultClusterRenderer<YourClusterItem>
and set it to ClusterManager as renderer like this:
mClusterManager.setRenderer(new YourClusterItemRenderer(...));
Override YourClusterItemRenderer's methods onBeforeClusterItemRendered and onBeforeClusterRendered and there you can change what each marker looks like according to your logic as per parameters from your server.
So when ClusterManager finishes rendering all your markers already look like you want them according to user state etc.
Related
I have a simple app that show a lot of markers on a map (Google Map). At the moment I have over 3,000 markers and this number keeps growing as the database gets bigger.
I also have a few checkboxes that dictate which markers are to be shown, vice versa.
Originally I was adding each marker like this (inside a loop going through every location)
Marker marker;
marker = mMap.addMarker (markerOptions);
mMarkerArrayList.add(marker);
Then when the user interacted with the checkboxes, I simply made markers visible/invisible. This is faster removing, and re-adding the markers.
The code in the Checkbox Change Listener was something like this:
for (Marker marker : mMarkerArrayList) {
if (condition) {
marker.setVisible(false);
}
}
And then make the visible again on another change of the checkbox.
However, adding all these markers individually in the beginning was taking a long time (1-2 seconds) and as the markers can only be added in the UI thread, it was freezing the UI (including my progress bar) for that amount of time.
After a lot of research, and not getting anywhere, the only thing I could do was add the markers using the ClusterManager (android-maps-utils library). This loads twice as fast, and does not block my UI.
Adding code is something like this (here myLocation is the object of my MyLocation class which holds the lcoation and relevant data for each point).
for (MyLocation myLocation : mMyLocationArrayList) {
mClusterManager.addItem(myLocation)
}
Now the issue is linking the checkboxes to these ClusterItem objects. I cannot find any method, or field in the library to change the visibility of these markers (ClusterItem). In the previous method (changing the visibility) was almost instantaneous (fast enough that the user would not feel any lag). However, now, I need to add and remove these markers everytime, and there is a lag, and it is very obvious to the user.
Does anyone have any suggestions?
You should be able to use mClusterManager.getMarkerCollection().getMarkers() in order to get the Marker collection, and then hide certain Markers in the same way as you were before:
Collection<Marker> markerCollection = mClusterManager.getMarkerCollection().getMarkers();
for (Marker marker : markerCollection) {
if (condition) {
marker.setVisible(false);
}
}
#Khash I have had some success with the following code (i.e. hides both the Markers and Clusters), although I experience a similar issue when I zoom the map in or out the Markers and Clusters re-appear.
Hope this helps, happy coding!
if (hide) {
/* Hide all Markers and Clusters */
mMyClusterManager.getMarkerCollection().hideAll();
mMyClusterManager.getClusterMarkerCollection().hideAll();
} else {
/* Show all Markers and Clusters */
mMyClusterManager.getMarkerCollection().showAll();
mMyClusterManager.getClusterMarkerCollection().showAll();
}
I'm creating a small app that will help a user find a sports club. I'm currently creating a club coordinate variable in the MapsActivity.java file like so:
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
LatLng lansdowneRFC = new LatLng(53.3334103,-6.2201649);
// Adds location to the map (includes a small bit of info about the club)
mMap.addMarker(new MarkerOptions().position(lansdowneRFC).title("Lansdowne RFC").snippet("Aviva Stadium, 62 Lansdowne Rd,"));
}
This produces the following result
I'm trying to add a button to the information box that will bring the user to another activity (The activity will be a sign up form so they can join the club). Considering the approach I'm using is it possible to implement a button or will I have to approach it in another way? I've seen ideas like a custom popup window be suggested for this type of thing but how I implement that into a google maps marker instead of a button is where I'm hitting a brick wall. Any suggestions
You can set your own implementation of InfoWindowAdapter to your GoogleMap object with a call to setInfoWindowAdapter().
And in the adapter you can override getInfoContents() to return whatever view you want.
But the down side is the view that is shown is not a live view, it's an image of the view you created, and you can handle the click on it using a OnInfoWindowClickListener that you set on the GoogleMap object with setOnInfoWindowClickListener().
But that handles the click on the whole window, not just the button.
Another option is to know where your marker is at the screen and show a popup manually above it that has nothing to do with the map, but I would not recommend that approach, with Android's different device sizes and all, that could quickly get very ugly.
I made simple application where I get markers based on screen coordinates from the server. Problem is that markers doesn't appear on map immediately. User user must zoom in or zoom out only then marker is visible. Why is that? Is it possible to show marker as soon when marker data is downloaded from server? Or is it possible to somehow refresh map? I know that in previous version there was method map.invalidate() that is basically what I need to have now. Unfortunately this method is not available now.
I will add code snippet how my markers is added
#Override
protected void onPostExecute(ArrayList<MarkerItemData> markerItemData) {
super.onPostExecute(markerItemData);
if(markerItemData != null) {
mClusterManager.addItems(markerItemData);
}
}
It needs re-clustering again.
mClusterManager.addItems(markerItemData);
mClusterManager.cluster();
Because, when you add or remove an ClusterItem (MarkerItemData), it just performs the algorithm and calculates clusters. But does not render on map
Finally, ClusterManager listens onCameraIdle event(includes zoom events) and invokes cluster() method internally. This is the answer of user must zoom in or zoom out only then marker is visible.
From the javadoc here, you need to recluster your markers after adding them to your ClusterManager doing mClusterManager.cluster().
Forgive me for being new to android app building.
My plan is to build an app that would take a city and open a new activity for it. The problem is I really don't know how to go about that. My plan would be very similar to how the app yik yak does it where you go put a marker in a certain area and it brings you to the activity for that location. I believe yik yak only shows the ones that are close to you but my plan was to take you to an activity for that location. Is that possible to do it that way or should I take yik yak's route on it and only show things that are within a certain radius of you?
Where can I get started on learning how to do that?
First, you need to choose a provider for your maps.
I would say google map is a good start as you suggested in the tag, but beware, it might not be available on some Chinese devices without play services.
You can follow the documentation here for a quick start
https://developers.google.com/maps/documentation/android/
Regarding the number of pins you want to put on your map, I see no problem to display a lot of them, you just have to group markers as the user zooms out, so you don't have 10 pins overlapping themselves.
So if in a quite zoomed state, the user clicks a group of markers, you can display a dialog to choose the city.
If the user zooms in a lot and clicks a particular pin, then you can start an activity.
Your main concern is listening for a zoom level change, like that:
mMap.setOnCameraChangeListener(new OnCameraChangeListener() {
private float currentZoom = -1;
#Override
public void onCameraChange(CameraPosition pos) {
if (pos.zoom != currentZoom){
currentZoom = pos.zoom;
// do you action here
}
}
});
Credits: https://stackoverflow.com/a/15046761/689710
Then, you can create a Map of Lists, the keys of the map will be the a custom lat / long object. Let's call it Pin. Pin provides it a custom isCloseTo method (taking a zoomlevel in params, and an other Pin object).
For each city you want to add on the map
For each key in the map
If the Pin object of your city isCloseTo the Pin key of the map
Add it to the list for that key
Else
Add a new Map entry with you city Pin as key and a list with your city as value
You Pin.isCloseTo method will be somehow similar to that:
https://stackoverflow.com/a/18170277/689710
Returning true or false according to "dist" return and your zoom level.
When you processed all your cities, you can browse the map and create a marker for each Pin key.
In a zoomed in state, you will have a Map with lists containing only one item, but i don't think it's much a problem.
You know yik yak is not starting a new activity when the place on the map is clicked. It rather updates the data underneath it when a new place is clicked. I think what you mean is you want to refresh/change the data displayed when a new city on google maps is clicked?
I want to build an interactive Android map app. It will have different marker types and lots of of different options when clicking on them.
First approach :
I started with the notion I will use custom infowindows but figured out that a map can have only single InfoWindowAdapter, with that said, this approach has another fault. InfoWindows can't have click listeners registered to them and I need to have some clickable UI to show after marker click.
Second approach :
Marker click triggers an alertDialog which corresponds to the marker type. I'm hesitant because I'll have lots of switch case inside the OnActivityResult.
Example - dialog fragments with OnActivityResult
Any other ideas ? Am I missing something ?
I ran into similar problem some time ago and I "hacked" it as follows:
mGoogleMap.setInfoWindowAdapter(new InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker pMarker) {
MarkerDescriptor descriptor = mMarkerDescriptorsMap.get(pMarker);
mGoogleMap.setOnInfoWindowClickListener(descriptor.getOnInfoWindowClickListener(MapActivity.this));
return descriptor.getInfoWindowView();
}
}
MarkerDescriptor should be simple interface that will be implemented for each specific marker type:
public interface MarkerDescriptor {
public View getInfoWindowView();
public OnInfoWindowClickListener getOnInfoWindowClickListener(Context pContext);
}
And to keep the references:
private Map<Marker, MarkerDescriptor> mMarkerDescriptorsMap = new HashMap<Marker, MarkerDescriptor>();
Basics of this idea is that GoogleMap can have only one marker selected at the time, so when user chooses another marker, we change the listeners.