Custom marker in google maps in android with vector asset icon
Asked Answered
D

9

107

How can we achieve a map marker icon with vector asset file, the way google shows it like this, programatically:

map

Update:

map.addMarker(new MarkerOptions()
    .position(latLng)
    .icon(BitmapDescriptorFactory.fromResource(R.drawable.your_vector_asset))
    .title(title);

this doesnot work when dealing with vector assets. The main reason to ask the question. The error with the above code:

java.lang.IllegalArgumentException: Failed to decode image. The provided image must be a Bitmap.

Doggoned answered 21/2, 2017 at 11:8 Comment(4)
@RishabhMahatha I can easily do it with png, but the problem is png is a heavy file. either I have to store it in my apk or have to call it from server. But I specifically need it from vector asset file.Doggoned
@LucaNicoletti I have to achieve this using vector asset(or SVG) file. and I am not getting any lib or method to do it.Doggoned
It can also be found in this answer: #33696988Danikadanila
there is an extension method Drawable.toBitmap() you can just it with BitmapDescriptorFactory.fromBitmap()Odessaodetta
D
51

I was looking for exact same requirement, and seeing this question made me happy at first, but same as @Shuddh I wasn't happy with the given answers.

To make my story short, I am using following code for this requirement:

private BitmapDescriptor bitmapDescriptorFromVector(Context context, @DrawableRes  int vectorDrawableResourceId) {
    Drawable background = ContextCompat.getDrawable(context, R.drawable.ic_map_pin_filled_blue_48dp);
    background.setBounds(0, 0, background.getIntrinsicWidth(), background.getIntrinsicHeight());
    Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorDrawableResourceId);
    vectorDrawable.setBounds(40, 20, vectorDrawable.getIntrinsicWidth() + 40, vectorDrawable.getIntrinsicHeight() + 20);
    Bitmap bitmap = Bitmap.createBitmap(background.getIntrinsicWidth(), background.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    background.draw(canvas);
    vectorDrawable.draw(canvas);
    return BitmapDescriptorFactory.fromBitmap(bitmap);
}

and a usage example:

.icon(bitmapDescriptorFromVector(this, R.drawable.ic_car_white_24dp));

Note: you may wish to use different bounding for your vectors, my vectors were 24dp in size and I've used a 48dp png image(blue-part, which can be a vector too) as background.

UPDATE: Adding screenshot as it was requested.

Screenshot for end result

Declinometer answered 20/1, 2018 at 13:23 Comment(5)
I think this will do the trick. I will check this and Mark the answer. But if you have a screenshot. Please share.Doggoned
I've add a screenshot, I hope that clarify what was the requirement originally! ;-) "I mean reuse vectors inside a Map pin"Declinometer
I think that calculations for the inner image bounds should be different (kotlin implementaion below): val left = (background.intrinsicWidth - vectorDrawable.intrinsicWidth) / 2 val top = (background.intrinsicHeight - vectorDrawable.intrinsicHeight) / 3 vectorDrawable.setBounds(left, top, left + vectorDrawable.intrinsicWidth, top + vectorDrawable.intrinsicHeight)Solly
They look really low-resolutionCycloparaffin
@Cycloparaffin I did guess someone will say that! :) and that's why I add this part to my answer: "I've used a 48dp png image(blue-part, which can be a vector too) as background"Declinometer
S
171

You can use this method:

private BitmapDescriptor bitmapDescriptorFromVector(Context context, int vectorResId) {
        Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorResId);
        vectorDrawable.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
        Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        vectorDrawable.draw(canvas);
        return BitmapDescriptorFactory.fromBitmap(bitmap);
}

So your code will look like:

map.addMarker(new MarkerOptions()
                .position(latLng)
                .icon(bitmapDescriptorFromVector(getActivity(), R.drawable.your_vector_asset))
                .title(title);

Edit:
In Kotlin it may look like:

private fun bitmapDescriptorFromVector(context: Context, vectorResId: Int): BitmapDescriptor? {
        return ContextCompat.getDrawable(context, vectorResId)?.run {
            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
            val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
            draw(Canvas(bitmap))
            BitmapDescriptorFactory.fromBitmap(bitmap)
        }
    }
Sandstone answered 8/8, 2017 at 9:47 Comment(13)
Have you tried it. I have not seen this code for a while now!Doggoned
Sure, I used it in my projectSandstone
This still does solve my issue! What your method is giving me is just that car icon and not the blue Background. I need to keep that background plus over that drar this icon.Doggoned
Do you mean that the car icon is a separate icon from the pin?Sandstone
Yes. That is the issue. Since I will have multiple of icons in place of the car icon.Doggoned
It makes sense to have a complete vector with the pin and the car. Otherwise you may try to wrap them into a drawable with layer lists and then convert that drawable into BitmapDescriptorSandstone
Can it be possible?Doggoned
Used this in my project to get a marker from vector asset.Precipitous
how efficiently is this solution?Urbano
@OllieStrevel As you can see, it creates a bitmap each time you call the method. So if you add a lot of similar markers or remove and add markers several times (according to your requirements), it's a good Idea to reuse generated BitmapDescriptors. You may add some logic and implement lazy initializationSandstone
@RishabhSrivastava, in my case API 19 emulator had no Google Play Services. Then I moved marker initialization to onMapReady().Czarist
any idea how to change the tint color for icons added this way?Consideration
there is an extension method Drawable.toBitmap() you can just it with BitmapDescriptorFactory.fromBitmap()Odessaodetta
D
51

I was looking for exact same requirement, and seeing this question made me happy at first, but same as @Shuddh I wasn't happy with the given answers.

To make my story short, I am using following code for this requirement:

private BitmapDescriptor bitmapDescriptorFromVector(Context context, @DrawableRes  int vectorDrawableResourceId) {
    Drawable background = ContextCompat.getDrawable(context, R.drawable.ic_map_pin_filled_blue_48dp);
    background.setBounds(0, 0, background.getIntrinsicWidth(), background.getIntrinsicHeight());
    Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorDrawableResourceId);
    vectorDrawable.setBounds(40, 20, vectorDrawable.getIntrinsicWidth() + 40, vectorDrawable.getIntrinsicHeight() + 20);
    Bitmap bitmap = Bitmap.createBitmap(background.getIntrinsicWidth(), background.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    background.draw(canvas);
    vectorDrawable.draw(canvas);
    return BitmapDescriptorFactory.fromBitmap(bitmap);
}

and a usage example:

.icon(bitmapDescriptorFromVector(this, R.drawable.ic_car_white_24dp));

Note: you may wish to use different bounding for your vectors, my vectors were 24dp in size and I've used a 48dp png image(blue-part, which can be a vector too) as background.

UPDATE: Adding screenshot as it was requested.

Screenshot for end result

Declinometer answered 20/1, 2018 at 13:23 Comment(5)
I think this will do the trick. I will check this and Mark the answer. But if you have a screenshot. Please share.Doggoned
I've add a screenshot, I hope that clarify what was the requirement originally! ;-) "I mean reuse vectors inside a Map pin"Declinometer
I think that calculations for the inner image bounds should be different (kotlin implementaion below): val left = (background.intrinsicWidth - vectorDrawable.intrinsicWidth) / 2 val top = (background.intrinsicHeight - vectorDrawable.intrinsicHeight) / 3 vectorDrawable.setBounds(left, top, left + vectorDrawable.intrinsicWidth, top + vectorDrawable.intrinsicHeight)Solly
They look really low-resolutionCycloparaffin
@Cycloparaffin I did guess someone will say that! :) and that's why I add this part to my answer: "I've used a 48dp png image(blue-part, which can be a vector too) as background"Declinometer
K
9

Here is the code for kotlin lovers ;)

private fun bitMapFromVector(vectorResID:Int):BitmapDescriptor {
    val vectorDrawable=ContextCompat.getDrawable(context!!,vectorResID)
    vectorDrawable!!.setBounds(0,0,vectorDrawable!!.intrinsicWidth,vectorDrawable.intrinsicHeight)
    val bitmap=Bitmap.createBitmap(vectorDrawable.intrinsicWidth,vectorDrawable.intrinsicHeight,Bitmap.Config.ARGB_8888)
    val canvas=Canvas(bitmap)
    vectorDrawable.draw(canvas)
    return BitmapDescriptorFactory.fromBitmap(bitmap)
}
Kv answered 2/5, 2020 at 6:23 Comment(1)
Have you had any problems with this snippet? Specially the setBounds part?Futures
D
8

If someone who is looking for in kotlin here is the method for you :

  private fun  bitmapDescriptorFromVector(vectorResId:Int):BitmapDescriptor {
            var vectorDrawable = ContextCompat.getDrawable(requireContext(), vectorResId);
            vectorDrawable!!.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
            var bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            var canvas =  Canvas(bitmap);
            vectorDrawable.draw(canvas);
            return BitmapDescriptorFactory.fromBitmap(bitmap);
}

above method will convert you vector icon to bitmapdescritor

map.addMarker(new MarkerOptions()
                .position(latLng)
                .icon(bitmapDescriptorFromVector(getActivity(), R.drawable.your_vector_asset))
                .title(title)

and this one for setting marker for your map thanks for Leo Droidcoder from his answer only i have converted it to kotlin

Deedee answered 2/7, 2019 at 7:23 Comment(0)
M
6

Might be a bit late to the game but this works great with Google Maps v2:

public static BitmapDescriptor getBitmapFromVector(@NonNull Context context,
                                                   @DrawableRes int vectorResourceId,
                                                   @ColorInt int tintColor) {

    Drawable vectorDrawable = ResourcesCompat.getDrawable(
            context.getResources(), vectorResourceId, null);
    if (vectorDrawable == null) {
        Log.e(TAG, "Requested vector resource was not found");
        return BitmapDescriptorFactory.defaultMarker();
    }
    Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
            vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    DrawableCompat.setTint(vectorDrawable, tintColor);
    vectorDrawable.draw(canvas);
    return BitmapDescriptorFactory.fromBitmap(bitmap);
}

Initialized as:

locationMarkerIcon = LayoutUtils.getBitmapFromVector(ctx, R.drawable.ic_location_marker,
                ContextCompat.getColor(ctx, R.color.marker_color));

Usage:

googleMap.addMarker(MarkerOptions().icon(getMarkerIcon()).position(latLng));

Note: getMarkerIcon() just returns the initialized non null locationMarkerIcon member variable.

Screenshot:

enter image description here

Mariettemarigold answered 11/4, 2018 at 17:12 Comment(4)
It's not what the question asks for. If you can see the screenshot attached at correct answer.Doggoned
@Doggoned basically my vector asset is a bit different in my screenshot (based on my project) but as far as I see, your original question asked for how to use a vector asset as a marker icon and that's what the static method does (along with also adding a color dynamically to it).Mariettemarigold
It's not adding colour dynamically. It's basically the change in the inner icon. If you can see the screenshot in selected answerDoggoned
Supperb! In my case I got rid of the tint color and it works! Additionally, I'd suggest modifyng marker's anchor as follows (if your's it's a classic pin): .anchor(0.5f, 1f);Aerate
N
6

In Kotlin: I used the below code to show the SVG image on Marker. Here, I used no background color / SVG.

fun getBitmapDescriptorFromVector(context: Context, @DrawableRes vectorDrawableResourceId: Int): BitmapDescriptor? {

    val vectorDrawable = ContextCompat.getDrawable(context, vectorDrawableResourceId)
    val bitmap = Bitmap.createBitmap(vectorDrawable!!.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
    vectorDrawable.draw(canvas)

    return BitmapDescriptorFactory.fromBitmap(bitmap)
}

Use like this way:

googleMap?.addMarker(MarkerOptions().position(LatLng(it.latitude!!, it.longitude!!))
            .title(it.airLineDetails))?.setIcon(
            getBitmapDescriptorFromVector(requireContext(), R.drawable.ic_flight_blue))

Screen Shot:

Navarrete answered 24/9, 2020 at 7:25 Comment(0)
C
2

convert vector resource to bitmap object and use BitmapDescriptorFactory.fromBitmap(bitmap)

   Bitmap bitmap = getBitmapFromVectorDrawable(getContext(),R.drawable.ic_pin);
   BitmapDescriptor descriptor =BitmapDescriptorFactory.fromBitmap(bitmap);
   MarkerOptions markerOptions = new MarkerOptions();
   markerOptions.icon(descriptor);

Bitmap converter:

 public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
        Drawable drawable =  AppCompatResources.getDrawable(context, drawableId)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            drawable = (DrawableCompat.wrap(drawable)).mutate();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }
Courage answered 23/4, 2019 at 6:5 Comment(2)
yes i was looking for this answer,which returns in bitmap format. :)Somnus
getting error: cannot find symbol: class BitmapDescriptorCocci
S
-1

Try this

MarkerOptions op = new MarkerOptions();
op.position(src_latlng);
Marker origin_marker = googleMap.addMarker(op);

Bitmap bitmap = getBitmap(this,R.drawable.ic_map_marker);
origin_marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap));

getBitmap

public Bitmap getBitmap(Context context, int drawableId) {
   Drawable drawable = ContextCompat.getDrawable(context, drawableId);
   if (drawable instanceof BitmapDrawable) {
       return BitmapFactory.decodeResource(context.getResources(), drawableId);
   } else if (drawable instanceof VectorDrawable) {
       return getBitmap((VectorDrawable) drawable);
   } else {
       throw new IllegalArgumentException("unsupported drawable type");
   }
}

ic_map_marker.xml

<vector android:height="32dp" android:viewportHeight="512.0"
    android:viewportWidth="512.0" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#f32f00" android:pathData="M288,284.8V480l-64,32V284.8c10.3,2.1 21,3.3 32,3.3S277.7,286.9 288,284.8zM384,128c0,70.7 -57.3,128 -128,128c-70.7,0 -128,-57.3 -128,-128S185.3,0 256,0C326.7,0 384,57.3 384,128zM256,64c0,-17.7 -14.3,-32 -32,-32s-32,14.3 -32,32s14.3,32 32,32S256,81.7 256,64z"/>
</vector>
Stammel answered 21/2, 2017 at 11:16 Comment(6)
Can you explain the vectorDrawable return statement. What is the getBitmap method you are using.Doggoned
Plus I don't think this will also give me the marker I want. as the marker shown in the image is default marker containing a vector asset. but this code will generate icon as per the vector asset, but not with-in the marker(the blue part).Doggoned
@Doggoned The icon that you are looking for would have to be custom. The vector asset should define the blue and the white car part. And then you will add that vector as icon.Ozalid
@PradumnKumarMahanta So I have to create another vector for background of icon. Right? But still how to set a vector as marker?Doggoned
@Doggoned No, You can create a vector for the complete thing. Marker + Image. And Add that vector as marker with the method stated by, Eswar or as Hiristo Stoyanov and I suggested, both are working for me.Ozalid
I am getting the error as shown in the question. Plus I need different kind of images in that marker.Doggoned
W
-1

For a Kotlin user.Please check below code.As I ddid in Fragment class.

class MapPinFragment : Fragment() {

    private lateinit var googleMap1: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_map_pin, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mapView.onCreate(savedInstanceState)
        mapView.onResume()

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)


        mapView.getMapAsync { googleMap ->
            googleMap1 = googleMap as GoogleMap
            addCustomMarker()
        }

    }

    private fun addCustomMarker() {
        Log.d("addCustomMarker", "addCustomMarker()")
        if (googleMap1 == null) {
            return
        }
        // adding a marker on map with image from  drawable
        googleMap1.addMarker(
            MarkerOptions()
                .position(LatLng(23.0225 , 72.5714))
                .icon(BitmapDescriptorFactory.fromBitmap(getMarkerBitmapFromView()))
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        if (mapView != null)
            mapView.onDestroy()
    }
    override fun onLowMemory() {
        super.onLowMemory()
        mapView.onLowMemory()
    }

    private fun getMarkerBitmapFromView(): Bitmap? {
        val customMarkerView: View? = layoutInflater.inflate(R.layout.view_custom_marker, null)
//        val markerImageView: ImageView =
//            customMarkerView.findViewById<View>(R.id.profile_image) as ImageView
        customMarkerView?.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED );
        customMarkerView?.layout(0, 0, customMarkerView.measuredWidth, customMarkerView.measuredHeight);
        customMarkerView?.buildDrawingCache();
        val returnedBitmap = Bitmap.createBitmap(
            customMarkerView!!.measuredWidth, customMarkerView.measuredHeight,
            Bitmap.Config.ARGB_8888
        )
        val canvas = Canvas(returnedBitmap)
        canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN)
        val drawable = customMarkerView.background

        drawable?.draw(canvas);
        customMarkerView.draw(canvas);
        return returnedBitmap;

    }




}
Wyoming answered 12/3, 2020 at 11:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.