191

I am developing an Android Application where I'm using Google Map API v2. I need to show the user location on a map with custom markers.

Each marker will show the picture of the user from an URL. The image must be downloaded in asynchronous mode from the server. See the attached screenshot for an example.

How do I add an image and custom information in the marker?

enter image description here

lambda
  • 3,295
  • 1
  • 26
  • 32
Jitendra Nath
  • 2,072
  • 3
  • 14
  • 15

4 Answers4

219

In the Google Maps API v2 Demo there is a MarkerDemoActivity class in which you can see how a custom Image is set to a GoogleMap.

// Uses a custom icon.
mSydney = mMap.addMarker(new MarkerOptions()
    .position(SYDNEY)
    .title("Sydney")
    .snippet("Population: 4,627,300")
    .icon(BitmapDescriptorFactory.fromResource(R.drawable.arrow)));

As this just replaces the marker with an image you might want to use a Canvas to draw more complex and fancier stuff:

Bitmap.Config conf = Bitmap.Config.ARGB_8888;
Bitmap bmp = Bitmap.createBitmap(80, 80, conf);
Canvas canvas1 = new Canvas(bmp);

// paint defines the text color, stroke width and size
Paint color = new Paint();
color.setTextSize(35);
color.setColor(Color.BLACK);

// modify canvas
canvas1.drawBitmap(BitmapFactory.decodeResource(getResources(),
    R.drawable.user_picture_image), 0,0, color);
canvas1.drawText("User Name!", 30, 40, color);

// add marker to Map
mMap.addMarker(new MarkerOptions()
    .position(USER_POSITION)
    .icon(BitmapDescriptorFactory.fromBitmap(bmp))
    // Specifies the anchor to be at a particular point in the marker image.
    .anchor(0.5f, 1));

This draws the Canvas canvas1 onto the GoogleMap mMap. The code should (mostly) speak for itself, there are many tutorials out there how to draw a Canvas. You can start by looking at the Canvas and Drawables from the Android Developer page.

Now you also want to download a picture from an URL.

URL url = new URL(user_image_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
conn.setDoInput(true);   
conn.connect();     
InputStream is = conn.getInputStream();
bmImg = BitmapFactory.decodeStream(is); 

You must download the image from an background thread (you could use AsyncTask or Volley or RxJava for that).

After that you can replace the BitmapFactory.decodeResource(getResources(), R.drawable.user_picture_image) with your downloaded image bmImg.

Santanu Sur
  • 10,997
  • 7
  • 33
  • 52
lambda
  • 3,295
  • 1
  • 26
  • 32
  • 22
    Excellent answer, except for the URL part. You will be downloading that image on the main application thread, which is an exceedingly bad idea. In fact, your code will crash outright on Android 4.0+ because of this. "Might" is too weak of a statement here -- you *must* download the image in a background thread, to avoid a janky UI, let alone the `NetworkOnMainThreadException`. – CommonsWare Feb 11 '13 at 12:51
  • 1
    @lambda k we download the images with background thread , one thing i did not understand sir when we get the latitude , longitude and url form the web service , then how to convert the url into bitmap in it so fast – Rishi Gautam Apr 26 '13 at 10:13
  • new AsyncTask() { protected void onPostExecute(Bitmap result) { image.setImageBitmap(result); }; @Override protected Bitmap doInBackground(Void... params) { return Utils.loadBitmap(url); } }.execute(); – sonida Apr 30 '13 at 03:45
  • 3
    this works but has performace issues, plus does not cache, handle memory issues, etc. Look into Google's Volley or Square's Picasso framework to have more efficient downloading. – Patrick Jackson May 28 '13 at 16:05
  • its not working for me the canvas is not drawing image on the map when the image is loaded from a url – Shinto Joseph May 24 '14 at 03:30
  • @Rishi The magic thing is that URL is fetch and processed by a server side script and returns just only the output from the client side, no processing is done in client side. – mr5 Jul 14 '14 at 08:17
  • 1
    Is is working in Lolipop ? I have implemented similar feature in my app which loads marker image from url which is working fine on all os versions but Lolipop. In Lolipop it does not render the downloaded icon. Does anyone has idea? – Vinay Apr 03 '15 at 07:22
  • Or alternatively you could implement a class GoogleMap.InfoWindowAdapter" and then setup a listener "GoogleMap.setInfoWindowAdapter()" to customize the marker options. http://www.rogcg.com/blog/2014/04/20/android-working-with-google-maps-v2-and-custom-markers – neaGaze Apr 13 '15 at 11:24
  • Not working. The method icon(BitmapDescriptor) in the type MarkerOptions is not applicable for the arguments (Bitmap) – Shailendra Madda May 14 '15 at 14:15
  • Only "problem" here is that it must first download a image to build the marker bitmap, which means it doesn't show all markers at once, but it shows sequentially as soon as it downloads the next image. – Teo Inke Jun 26 '15 at 14:22
  • canvas.drawtext is cutting of the text. Full text is not drawing http://stackoverflow.com/questions/39306724/how-to-show-markericon-and-tittle-in-google-map-as-like-google-apps – Naveen Kumar M Sep 06 '16 at 11:08
  • @lambda Maybe you know how to solve related problem: https://stackoverflow.com/questions/69108723/google-maps-custom-markers-overlap-issue ? – Oleh Liskovych Sep 08 '21 at 19:57
104

The alternative and easier solution that i also use is to create custom marker layout and convert it into a bitmap.

view_custom_marker.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/custom_marker_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/marker_mask">

    <ImageView
        android:id="@+id/profile_image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_gravity="center_horizontal"
        android:contentDescription="@null"
        android:src="@drawable/avatar" />
</FrameLayout>

Convert this view into bitmap by using the code below

 private Bitmap getMarkerBitmapFromView(@DrawableRes int resId) {

        View customMarkerView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.view_custom_marker, null);
        ImageView markerImageView = (ImageView) customMarkerView.findViewById(R.id.profile_image);
        markerImageView.setImageResource(resId);
        customMarkerView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        customMarkerView.layout(0, 0, customMarkerView.getMeasuredWidth(), customMarkerView.getMeasuredHeight());
        customMarkerView.buildDrawingCache();
        Bitmap returnedBitmap = Bitmap.createBitmap(customMarkerView.getMeasuredWidth(), customMarkerView.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);
        canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN);
        Drawable drawable = customMarkerView.getBackground();
        if (drawable != null)
            drawable.draw(canvas);
        customMarkerView.draw(canvas);
        return returnedBitmap;
    }

Add your custom marker in on Map ready callback.

@Override
public void onMapReady(GoogleMap googleMap) {
    Log.d(TAG, "onMapReady() called with");
    mGoogleMap = googleMap;
    MapsInitializer.initialize(this);
    addCustomMarker();
}
private void addCustomMarker() {
    Log.d(TAG, "addCustomMarker()");
    if (mGoogleMap == null) {
        return;
    }

    // adding a marker on map with image from  drawable
   mGoogleMap.addMarker(new MarkerOptions()
            .position(mDummyLatLng)
            .icon(BitmapDescriptorFactory.fromBitmap(getMarkerBitmapFromView(R.drawable.avatar))));
}
waleedsarwar86
  • 2,354
  • 1
  • 19
  • 21
16

I hope it still not too late to share my solution. Before that, you can follow the tutorial as stated in Android Developer documentation. To achieve this, you need to use Cluster Manager with defaultRenderer.

  1. Create an object that implements ClusterItem

    public class SampleJob implements ClusterItem {
    
    private double latitude;
    private double longitude;
    
    //Create constructor, getter and setter here
    
    @Override
    public LatLng getPosition() {
        return new LatLng(latitude, longitude);
    }
    
  2. Create a default renderer class. This is the class that do all the job (inflating custom marker/cluster with your own style). I am using Universal image loader to do the downloading and caching the image.

    public class JobRenderer extends DefaultClusterRenderer< SampleJob > {
    
    private final IconGenerator iconGenerator;
    private final IconGenerator clusterIconGenerator;
    private final ImageView imageView;
    private final ImageView clusterImageView;
    private final int markerWidth;
    private final int markerHeight;
    private final String TAG = "ClusterRenderer";
    private DisplayImageOptions options;
    
    
    public JobRenderer(Context context, GoogleMap map, ClusterManager<SampleJob> clusterManager) {
        super(context, map, clusterManager);
    
        // initialize cluster icon generator
        clusterIconGenerator = new IconGenerator(context.getApplicationContext());
        View clusterView = LayoutInflater.from(context).inflate(R.layout.multi_profile, null);
        clusterIconGenerator.setContentView(clusterView);
        clusterImageView = (ImageView) clusterView.findViewById(R.id.image);
    
        // initialize cluster item icon generator
        iconGenerator = new IconGenerator(context.getApplicationContext());
        imageView = new ImageView(context.getApplicationContext());
        markerWidth = (int) context.getResources().getDimension(R.dimen.custom_profile_image);
        markerHeight = (int) context.getResources().getDimension(R.dimen.custom_profile_image);
        imageView.setLayoutParams(new ViewGroup.LayoutParams(markerWidth, markerHeight));
        int padding = (int) context.getResources().getDimension(R.dimen.custom_profile_padding);
        imageView.setPadding(padding, padding, padding, padding);
        iconGenerator.setContentView(imageView);
    
        options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.circle_icon_logo)
                .showImageForEmptyUri(R.drawable.circle_icon_logo)
                .showImageOnFail(R.drawable.circle_icon_logo)
                .cacheInMemory(false)
                .cacheOnDisk(true)
                .considerExifParams(true)
                .bitmapConfig(Bitmap.Config.RGB_565)
                .build();
    }
    
    @Override
    protected void onBeforeClusterItemRendered(SampleJob job, MarkerOptions markerOptions) {
    
    
        ImageLoader.getInstance().displayImage(job.getJobImageURL(), imageView, options);
        Bitmap icon = iconGenerator.makeIcon(job.getName());
        markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(job.getName());
    
    
    }
    
    @Override
    protected void onBeforeClusterRendered(Cluster<SampleJob> cluster, MarkerOptions markerOptions) {
    
        Iterator<Job> iterator = cluster.getItems().iterator();
        ImageLoader.getInstance().displayImage(iterator.next().getJobImageURL(), clusterImageView, options);
        Bitmap icon = clusterIconGenerator.makeIcon(iterator.next().getName());
        markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
    }
    
    @Override
    protected boolean shouldRenderAsCluster(Cluster cluster) {
        return cluster.getSize() > 1;
    }
    
  3. Apply cluster manager in your activity/fragment class.

    public class SampleActivity extends AppCompatActivity implements OnMapReadyCallback {
    
    private ClusterManager<SampleJob> mClusterManager;
    private GoogleMap mMap;
    private ArrayList<SampleJob> jobs = new ArrayList<SampleJob>();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_landing);
    
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }
    
    
    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        mMap.getUiSettings().setMapToolbarEnabled(true);
        mClusterManager = new ClusterManager<SampleJob>(this, mMap);
        mClusterManager.setRenderer(new JobRenderer(this, mMap, mClusterManager));
        mMap.setOnCameraChangeListener(mClusterManager);
        mMap.setOnMarkerClickListener(mClusterManager);
    
        //Assume that we already have arraylist of jobs
    
    
        for(final SampleJob job: jobs){
            mClusterManager.addItem(job);
        }
        mClusterManager.cluster();
    }
    
  4. Result

Result

Amad Yus
  • 2,856
  • 1
  • 24
  • 32
  • Could you list the dependencies for this approach? A lot of the code base isn't accounted for. – Jonathan Dunn Nov 23 '17 at 23:36
  • Is the 'Job' class you reference from the 'com.evernote:android-job:1.2.1' lib? – Jonathan Dunn Nov 23 '17 at 23:40
  • @JonDunn 'Job' in this sample is the custom class. This is the dependency that I used: compile 'com.google.maps.android:android-maps-utils:0.5+. More explanation can be found here: https://developers.google.com/maps/documentation/android-api/utility/marker-clustering#introduction – Amad Yus Nov 24 '17 at 03:36
2

From lambda answer, I have made something closer to the requirements.

boolean imageCreated = false;

Bitmap bmp = null;
Marker currentLocationMarker;
private void doSomeCustomizationForMarker(LatLng currentLocation) {
    if (!imageCreated) {
        imageCreated = true;
        Bitmap.Config conf = Bitmap.Config.ARGB_8888;
        bmp = Bitmap.createBitmap(400, 400, conf);
        Canvas canvas1 = new Canvas(bmp);

        Paint color = new Paint();
        color.setTextSize(30);
        color.setColor(Color.WHITE);

        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inMutable = true;

        Bitmap imageBitmap=BitmapFactory.decodeResource(getResources(),
                R.drawable.messi,opt);
        Bitmap resized = Bitmap.createScaledBitmap(imageBitmap, 320, 320, true);
        canvas1.drawBitmap(resized, 40, 40, color);

        canvas1.drawText("Le Messi", 30, 40, color);

        currentLocationMarker = mMap.addMarker(new MarkerOptions().position(currentLocation)
                .icon(BitmapDescriptorFactory.fromBitmap(bmp))
                // Specifies the anchor to be at a particular point in the marker image.
                .anchor(0.5f, 1));
    } else {
        currentLocationMarker.setPosition(currentLocation);
    }

}
Muhammad Adil
  • 4,358
  • 3
  • 32
  • 36