I recently implemented clustering in my Android app, and I have managed to set a dynamic image for each marker with Picasso. However, not all markers have an image, and those that do not have it must be displayed with a custom marker my designer has provided.
This is the custom renderer I use:
private class CustomRenderer extends DefaultClusterRenderer<MyMarker>{
private IconGenerator iconGenerator;
private ImageView imageView;
CustomRenderer() {
super(ShopsMapActivity.this, map, clusterManager);
iconGenerator = new IconGenerator(ShopsMapActivity.this);
imageView = new ImageView(ShopsMapActivity.this.getApplicationContext());
imageView.setLayoutParams(new ViewGroup.LayoutParams(PIN_SIZE, PIN_SIZE));
int padding = Global.getXPercentOfWidth(1f);
imageView.setPadding(padding, padding, padding, padding);
iconGenerator.setContentView(imageView);
}
#Override
protected void onBeforeClusterItemRendered(MyMarker item, MarkerOptions markerOptions) {
if(item.hasLogo()) {
// this part works perfectly
loadAsync(imageView, item.getLogo());
try {
Bitmap icon = iconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
icon.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}else {
// this is the part that does not work. It should display a custom pin, but instead it displays a small empty white marker that should hold an image from Picasso. Problem is that there is no image for Picasso to download
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(createDefaultIconBitmap()));
}
}
private void loadAsync(final ImageView imageView, final String url) {
Picasso.with(ShopsMapActivity.this)
.load(url)
.into(imageView);
}
}
Related
I want to change clustering icon like below in android. In a circle there will be a one imageview and one textview.
My code for custom icon
private class ItemRenderer extends DefaultClusterRenderer<ClusterPopupList> {
private final IconGenerator mIconGenerator = new IconGenerator(getApplicationContext());
private final IconGenerator mClusterIconGenerator = new IconGenerator(getApplicationContext());
private final int mDimension;
public ItemRenderer() {
super(getApplicationContext(), map, mClusterManager);
View multiProfile = getLayoutInflater().inflate(R.layout.multi_profile,null);
mClusterIconGenerator.setContentView(multiProfile);
mImageView = new ImageView(getApplicationContext());
mDimension = (int) getResources().getDimension(R.dimen.custom_profile_image);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(mDimension, mDimension));
mIconGenerator.setContentView(mImageView);
}
#Override
protected void onBeforeClusterItemRendered(ClusterPopupList item, MarkerOptions markerOptions) {
mImageView.setImageResource(item.profilePhoto);
Bitmap icon = mIconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
super.onBeforeClusterItemRendered(item, markerOptions);
}
#Override
protected void onBeforeClusterRendered(Cluster<ClusterPopupList> cluster, MarkerOptions markerOptions) {
List<Drawable> profilePhotos = new ArrayList<Drawable>(Math.min(4, cluster.getSize()));
int width = mDimension;
int height = mDimension;
for (ClusterPopupList p : cluster.getItems()) {
// Draw 4 at most.
if (profilePhotos.size() == 4) break;
Drawable drawable = getResources().getDrawable(p.profilePhoto);
drawable.setBounds(0, 0, width, height);
profilePhotos.add(drawable);
}
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
}
#Override
protected void onClusterRendered(Cluster<ClusterPopupList> cluster, Marker marker) {
super.onClusterRendered(cluster, marker);
}
#Override
public ClusterPopupList getClusterItem(Marker marker) {
return super.getClusterItem(marker);
}
#Override
protected boolean shouldRenderAsCluster(Cluster<ClusterPopupList> cluster) {
return cluster.getSize() > 1;
}
}
multi_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#android:color/transparent"
android:src="#drawable/icon_cluster_count"/>
<TextView
android:id="#id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/Bubble.TextAppearance.Light"
android:paddingLeft="#dimen/custom_profile_padding"
android:paddingRight="#dimen/custom_profile_padding"
android:layout_below="#id/image"
android:text="150"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dip"
android:textColor="#color/theme_color"
android:alpha=".8"/>
</RelativeLayout>
Output :
I want to achieve cluster icon like first image in a circle. I have used circle background in relative layout, but that does not work.
Use this to remove background
mClusterIconGenerator.setBackground(null)
Set the background to a given Drawable, or remove the background.
#param background the Drawable to use as the background, or null to remove the background.
mClusterIconGenerator.setBackground(AndroidUtil.getDrawable(R.drawable.cluster_backgroud));
From the Google Maps documentation site:
Customize the marker clusters
The ClusterManager constructor creates a DefaultClusterRenderer and a NonHierarchicalDistanceBasedAlgorithm. You can change the ClusterRenderer and the Algorithm using the setAlgorithm(Algorithm<T> algorithm) and setRenderer(ClusterRenderer<T> view) methods of ClusterManager.
You can implement ClusterRenderer to customize the rendering of the clusters. DefaultClusterRenderer provides a good base to start from. By subclassing DefaultClusterRenderer, you can override the defaults.
I'm trying to layout a custom infoWindow programmatically. I want to load a streetView preview image using Picasso but the image isn't showing up, any idea why?
private View prepareInfoView(Marker marker){
//prepare InfoView programmatically
LinearLayout infoView = new LinearLayout(EarthquakeActivity.this);
LinearLayout.LayoutParams infoViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
infoView.setOrientation(LinearLayout.VERTICAL);
// attach the above layout to the infoView
infoView.setLayoutParams(infoViewParams);
//create street view preview # top
ImageView streetViewPreviewIV = new ImageView(EarthquakeActivity.this);
// this scales the image to match parents WIDTH?, but retain image's height??
LinearLayout.LayoutParams streetViewImageViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
streetViewPreviewIV.setLayoutParams(streetViewImageViewParams);
String imageURL = "https://maps.googleapis.com/maps/api/streetview?size=200x200&location=";
String markerLongitude = Double.toString(marker.getPosition().longitude);
String markerLatitude = Double.toString(marker.getPosition().latitude);
imageURL += markerLatitude + "," + markerLongitude + "&fov=120&heading=0&pitch=0";
Log.wtf("prepareInfoView", imageURL);
Picasso.with(this).load(imageURL).into(streetViewPreviewIV);
infoView.addView(streetViewPreviewIV);
I've tried with and without the api key appending the url.
It did work for a few clicks without the key, but hasn't since, with or without. Is the because it's too slow fetching it so Android gives up and loads the info window without it? Is there a best in class way to do this?
Would another image loading library work better? Google's volley?
Also with
LinearLayout.LayoutParams
I'd like the image to stretch across the width of the info windows, i.e. match_parent, and to scale vertically to maintain original aspect ratio, how do I do this?
This is my answer
In commonsWare new class I add this flag:
#Override
public void onSuccess() {
Log.i(TAG, "image got, should rebuild window");
if (marker != null && marker.isInfoWindowShown()) {
Log.i(TAG, "conditions met, redrawing window");
marker.setTag(new Boolean("True"));
marker.showInfoWindow();
}
}
And in prepareInfoView, I test for the flags absence.
if (marker.getTag() == null ) {
Log.i("prepareInfoView", "fetching image");
Picasso.with(this).load(imageURL).fetch(new MarkerCallback(marker));
}
else {
Log.wtf("prepareInfoView", "building info window");
Party on! :)
Is the because it's too slow fetching it so Android gives up and loads the info window without it?
Picasso loads asynchronously unless the image is cached. And the way Maps V2 works is that the View you return is converted into a Bitmap, and that is what gets rendered. As a result, you have a race condition between Picasso and Maps V2 (does the image get loaded before the Bitmap gets created?), and so it is indeterminate as to whether or not any given info window will work.
You can call showInfoWindow() on the Marker after Picasso has loaded the image, so you can populate the ImageView from Picasso's cache. showInfoWindow(), called on a Marker, triggers Maps V2 to regenerate the info window.
For example, you could change your existing into() call into into(streetViewPreviewIV, new MarkerCallback(marker)), with a MarkerCallback like:
static class MarkerCallback implements Callback {
Marker marker=null;
MarkerCallback(Marker marker) {
this.marker=marker;
}
#Override
public void onError() {
Log.e(getClass().getSimpleName(), "Error loading thumbnail!");
}
#Override
public void onSuccess() {
if (marker != null && marker.isInfoWindowShown()) {
marker.showInfoWindow();
}
}
}
Would another image loading library work better? Google's volley?
They will all suffer from the same issue.
What is working for me is this:
public class MarkerCallback implements Callback {
Marker marker=null;
String URL;
ImageView userPhoto;
MarkerCallback(Marker marker, String URL, ImageView userPhoto) {
this.marker=marker;
this.URL = URL;
this.userPhoto = userPhoto;
}
#Override
public void onError() {
//Log.e(getClass().getSimpleName(), "Error loading thumbnail!");
}
#Override
public void onSuccess() {
if (marker != null && marker.isInfoWindowShown()) {
marker.hideInfoWindow();
Picasso.with(getActivity())
.load(URL)
.into(userPhoto);
marker.showInfoWindow();
}
}
}
All I figured out is,
Picasso loads image asynchronously, so when a marker shows it's info window after clicking by internally calling the method getInfoContents or getInfoWindow method ,
by this time if the image isn't already downloaded or cached by Picasso , then it is not showed on infoWindow.
Picasso tries to load the image into imageview of infoWindow when downloaded, but According to Google maps V2, the infoWindows Once loaded, can't be manipulated, so image is not shown updated on the UI.
But the infowindow view was updated actually but couldn't show for the restriction, so if you just hide and show the infowindow , it is kind of refreshed, and the images are shown on updated infoWindow. you can do this in the following way,
You need to keep the marker reference, you can keep this as Activity/Fragment's member variable.
Picasso.with(context)
.load(marker.getSnippet())
.placeholder(R.drawable.ic_placeholder)
.into(imageView, new Callback() {
#Override
public void onSuccess() {
if (currentClickedMarker != null && currentClickedMarker.isInfoWindowShown()) {
//toggle the marker's infoWindow
currentClickedMarker.hideInfoWindow();
currentClickedMarker.showInfoWindow();
}
}
#Override
public void onError() {
}
});
I struggled with this as well, here is a solution with glide inspired from the accepted answer.
This solution did not work for me without resizing the picture to a proper size. With override() (and centerCrop) it did the trick.
Keep track of the latest picture shown
private String previousImageUrl = null;
And use it to see if you need refreshing of the current image
googleMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker marker) {
return null;
}
#Override
public View getInfoContents(final Marker marker) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_map_info_window, null);
MyObject myObject = (MyObject) marker.getTag();
final String url = myObject.getImageUrl();
final ImageView imageView = (ImageView) view.findViewById(R.id.image_view);
GlideApp.with(getContext()).load(url)
.override(imageWidth, imageHeight) // made the difference
.centerCrop()
.into(new SimpleTarget<Drawable>() {
#Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
imageView.setImageDrawable(resource);
if (!TextUtils.equals(url, previousImageUrl)) {
previousImageUrl = url;
marker.showInfoWindow();
}
}
});
return view;
}
});
If you are using the accepted answer to fix the problem and it still doesn't work,
you're probably using .fit() .
in other words you should remove .fit() from your Picasso code.
It took me a couple hours to realize it.
I am really struggling with google map util these days challenge after challenge and there is not good example or solution on the net.
This is my code:
#Override
protected void onBeforeClusterRendered(Cluster<ItemCluster> cluster,
MarkerOptions markerOptions) {
View marker = (getActivity()
.getLayoutInflater())
.inflate(R.layout.info_windows, null);
Bitmap bitmap = createDrawableFromView(
getActivity(), marker);
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));
}
The problem is before I send the view to this function to make a bitmap for marker, I try to set some data in my info_windows.xml which is included with some ImageViews and TextViews. But the app hangs, do you have any idea how to make this done?
Bitmap bitmap = createDrawableFromView(
getActivity(), marker);
I solve my problem by making an IconGenerator objectand then make the view from that this is how i did it:
first i made the object:
private final IconGenerator mClusterIconGenerator = new IconGenerator(getActivity().getApplicationContext());
and a view which contains our XML view file of our costum marker:
View markerIcon = getActivity().getLayoutInflater().inflate(R.layout.marker_icon, null);
then set the view on icongenerator:
mMarkerIconGenerator.setContentView(markerIcon);
then you need to initiate your imageview, textview, ... then set the values that you want to show inside your costum marker like this for ex:
mMarkerViento = (ImageView) clusterIcon.findViewById(R.id.viento);
then inside the ovveride method of:
#Override
protected void onBeforeClusterItemRendered(ItemCluster item, MarkerOptions markerOptions)
mMarkerViento.setImageResource(R.drawable.viento_ne2);
and at the end you generate your bitmap object from icon generator like this:
mMarkerIconGenerator.setBackground(TRANSPARENT_DRAWABLE); // set the background as transparent
Bitmap bitmap = mMarkerIconGenerator.makeIcon(); // make a bitmap object from the icon object
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)); // set the bitmap as marker icon
good luck and tanx
So I'm using a ClusterManagerto cluster my Markers, so that the user can have a better experience.
I have actually implemente Google's code, which I found here. Imagine now that my Marker icon is a ball. I want the background of the icon to be transparent, not white.
On Google's original tutorial they set a ImageView to the IconGenerator, like this:
public class MyClusterManagerRenderer extends DefaultClusterRenderer<ClusteredMarker> {
private final IconGenerator mIconGenerator;
private final ImageView mImageView;
public MyClusterManagerRenderer(Context context, GoogleMap googleMap,
ClusterManager<ClusteredMarker> clusterManager){
super(context, googleMap, clusterManager);
mIconGenerator = new IconGenerator(context);
mImageView = new ImageView(context);
mIconGenerator.setContentView(mImageView);
}
#Override
protected void onBeforeClusterItemRendered(ClusteredMarker item, MarkerOptions markerOptions) {
mImageView.setImageResource(item.iconPicture);
Bitmap icon = mIconGenerator.makeIcon();
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(item.user);
}
...
}
I have tried several ways to make my icon transparent, like calling:
mImageView.setBackgroundColor(Color.TRANSPARENT);
but without success. The only way I managed to find a solution is to directly attach my transparent image to the IconGenerator, like this:
mIconGenerator.setBackground(aContext.getResources().getDrawable(R.drawable.ball));
The downside of this approach is that a ImageView have some interesting methods that I would like to call, like setPadding, while the IconGenerator doesn't have that.
So, is there a way to make my icon transparent, using the ImageView?
Thank you,
The solution I found is this:
private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
// Make the background of marker transparent
mIconGenerator.setBackground(TRANSPARENT_DRAWABLE);
mImageView.setBackgroundColor(null);
That will remove the background and make it transparent. :)
I'm using Google Map SDK 7.3.0 with android-maps-utils 0.3.4 because I need clusters for my Markers on the map.
Ok, so here the problem is, I shouldn't have a red marker. Only green+blue markers.
I subclassed DefaultClusterRenderer to create my custom marker view but sometimes it just doesn't work.
I'm using picasso to get the green icon because it's coming from an API. But the problem is, when picasso has loaded the bitmap it's too late, the icon has already been set to the default one (red).
Here's my onBeforeClusterItemRenderer :
Picasso.with(getApplicationContext()).load(item.url).into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
FrameLayout icon = (FrameLayout) LayoutInflater.from(getApplicationContext()).inflate(R.layout.marker, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
icon.findViewById(R.id.bg).setBackground(new BitmapDrawable(getResources(), bitmap));
} else {
icon.findViewById(R.id.bg).setBackgroundDrawable(new BitmapDrawable(getResources(), bitmap));
}
Bitmap b = createDrawableFromView(Home.this, icon);
if (marker != null) {
marker.icon(BitmapDescriptorFactory.fromBitmap(b));
}
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
--- EDITED ---
When downloading the image inside onBeforeClusterItemRendered you are actually downloading the image every time the Cluster Manager tries to load a marker, so if you have, for example, 100 markers you will download the image 100 times.
You should download the image inside onCreate, save it in a static variable, call mClusterManager.cluster(); after saving the image, and finally inside onBeforeClusterItemRendered wrtie marker.icon(BitmapDescriptorFactory.fromBitmap(YourActivity.b));