安卓中使用矢量图标自定义谷歌地图标记

106

如何通过矢量资源文件实现地图标记图标,就像谷歌这样以编程方式显示:

map

更新:

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

当处理矢量资源时,这不起作用。问这个问题的主要原因。上述代码的错误为:

java.lang.IllegalArgumentException: 解码图像失败。提供的图像必须是位图。


@RishabhMahatha 我可以轻松地使用PNG完成它,但问题是PNG是一个较重的文件。我要么必须将其存储在我的APK中,要么必须从服务器调用它。但我特别需要来自矢量素材文件的这个。 - Shuddh
@LucaNicoletti 我必须使用矢量资产(或SVG)文件来实现这一点。但我找不到任何库或方法来完成它。 - Shuddh
有人了解吗?找到什么东西了吗? - Shuddh
1
它也可以在这个答案中找到:https://dev59.com/c1sX5IYBdhLWcg3whv7Q - Erfan
有一个扩展方法Drawable.toBitmap(),你可以将它与BitmapDescriptorFactory.fromBitmap()一起使用。 - gturedi
9个回答

169

你可以使用这种方法:

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);
}

那么你的代码将会是这样:

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

编辑
在 Kotlin 中可能如下:

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)
        }
    }

1
你试过了吗?我已经有一段时间没看到这段代码了! - Shuddh
1
这仍然没有解决我的问题!您的方法只给了我那个汽车图标,而没有蓝色背景。我需要保留那个背景,并在其上绘制这个图标。 - Shuddh
是的,这就是问题所在。因为我将会有多个图标代替汽车图标。 - Shuddh
最好将引脚和汽车放在一个完整的向量中。否则,您可以尝试将它们包装到具有图层列表的可绘制对象中,然后将该可绘制对象转换为BitmapDescriptor。 - Leo DroidCoder
2
在我的项目中使用了这个来获取向量资源中的标记。 - anoo_radha
显示剩余8条评论

50

我正在寻找完全相同的需求,看到这个问题起初让我感到高兴,但和@Shuddh一样,我对给出的答案不满意。

为了简短叙述我的故事,我正在使用以下代码来实现此需求:

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);
}

以及一个用法示例:

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

注意:您可能希望为向量使用不同的边界框,我的向量大小为24dp,并且我已将48dp的png图像(蓝色部分,也可以是向量)用作背景。

更新:如要求添加屏幕截图。

最终结果的屏幕截图


我觉得这个会有用。我会检查一下并标记答案。但如果你有截图,请分享。 - Shuddh
我已经添加了一张截图,希望能够澄清最初的需求!;-) “我的意思是在地图标记内重复使用向量” - SidMorad
3
我认为内部图像边界的计算方法应该不同(以下是Kotlin实现):val left = (background.intrinsicWidth - vectorDrawable.intrinsicWidth) / 2 val top = (background.intrinsicHeight - vectorDrawable.intrinsicHeight) / 3 vectorDrawable.setBounds(left, top, left + vectorDrawable.intrinsicWidth, top + vectorDrawable.intrinsicHeight) - gswierczynski
它们看起来分辨率非常低。 - xjcl
@xjcl 我猜到有人会这么说! :) 这就是为什么我在我的答案中添加了这一部分:“我使用了一个48dp的png图像(蓝色部分,也可以是矢量图形)作为背景”。 - SidMorad

9

这是给Kotlin爱好者的代码 ;)

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)
}

2
你在这段代码中遇到了什么问题吗?特别是 setBounds 部分? - ravi

8

如果有在寻找Kotlin相关内容的人,这里提供一个方法:

  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);
}

上述方法将把你的矢量图标转换为位图描述符。
map.addMarker(new MarkerOptions()
                .position(latLng)
                .icon(bitmapDescriptorFromVector(getActivity(), R.drawable.your_vector_asset))
                .title(title)

这段代码是用于在地图上设置标记的,感谢 Leo Droidcoder 的答案,我将其转换为 Kotlin 代码。


6
可能有点晚了,但这对于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);
}

初始化为:

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

使用方法:

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

注意:getMarkerIcon() 只是返回已初始化的非空 locationMarkerIcon 成员变量。
截图: enter image description here

1
这不是问题所要求的。如果您能在正确答案中看到附加的截图。 - Shuddh
2
@Shuddh 基本上,我的矢量资产在截图中有些不同(基于我的项目),但据我所见,您最初的问题是如何将矢量资产用作标记图标,这就是静态方法所做的事情(同时还可以动态地添加颜色)。 - prerak
它并没有动态添加颜色。基本上是内部图标的变化。如果您可以在所选答案中看到截图。 - Shuddh
太棒了!在我的情况下,我去掉了色调颜色,它起作用了!此外,如果您的标记是经典图钉,我建议将其锚点修改为.anchor(0.5f, 1f); - joninx

6
在 Kotlin 中:我使用以下代码在标记上显示 SVG 图像。这里,我没有使用背景色 / 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)
}

使用方法如下:

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

截图:


2
将矢量资源转换为位图对象并使用BitmapDescriptorFactory.fromBitmap(bitmap)。最初的回答。
   Bitmap bitmap = getBitmapFromVectorDrawable(getContext(),R.drawable.ic_pin);
   BitmapDescriptor descriptor =BitmapDescriptorFactory.fromBitmap(bitmap);
   MarkerOptions markerOptions = new MarkerOptions();
   markerOptions.icon(descriptor);

位图转换器:

 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;
    }

1
是的,我正在寻找以位图格式返回答案的方法。 :) - Ramkesh Yadav
获取error: 无法找到符号:class BitmapDescriptor - Arpit Bhalla

-1

针对Kotlin用户,请检查下面的代码。就像我在Fragment类中所做的那样。

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;

    }




}

-1

试试这个

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));

获取位图

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>

你能解释一下vectorDrawable返回语句吗?你正在使用什么getBitmap方法? - Shuddh
而且我认为这也不会给我想要的标记。因为图像中显示的标记是包含矢量资产的默认标记。但是这段代码将根据矢量资产生成图标,但不包括标记(蓝色部分)。 - Shuddh
@Shuddh 你要寻找的图标必须是自定义的。矢量资源应该定义蓝色和白色的汽车部件,然后将该矢量作为图标添加。 - Q2x13
@PradumnKumarMahanta 那我需要为图标的背景创建另一个向量,对吧?但是如何将向量设置为标记呢? - Shuddh
@Shuddh 不,你可以为整个东西创建一个向量。标记+图像。然后按照 Eswar 所述的方法将该向量作为标记添加,或者像 Hiristo Stoyanov 和我建议的那样进行操作,这两种方法对我都有效。 - Q2x13
我在问题中看到了错误。此外,我需要在该标记中使用不同类型的图像。 - Shuddh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接