使用VectorDrawable作为GoogleMap BitmapDescriptor

37

我在使用 Google 地图 MarkerOptions 创建图标时遇到问题,具体是关于 BitmapDescriptorVectorDrawable 在 API 5.0+ 上的使用。

创建方法如下:

@NonNull
private BitmapDescriptor getBitmapDescriptor(int id) {
    return BitmapDescriptorFactory.fromResource(id);
}

如果我尝试使用在xml中定义的VectorDrawable作为id参数,一切都很顺利,但是如果googleMap.addMarker(marker)BitmapDescriptor不为空,应用程序总是会崩溃。

11-05 10:15:05.213 14536-14536/xxx.xxxxx.app E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xxx.xxxxx.app, PID: 14536
    java.lang.NullPointerException
        at com.google.a.a.ae.a(Unknown Source)
        at com.google.maps.api.android.lib6.d.dn.<init>(Unknown Source)
        at com.google.maps.api.android.lib6.d.dm.a(Unknown Source)
        at com.google.maps.api.android.lib6.d.ag.<init>(Unknown Source)
        at com.google.maps.api.android.lib6.d.eu.a(Unknown Source)
        at com.google.android.gms.maps.internal.j.onTransact(SourceFile:167)
        at android.os.Binder.transact(Binder.java:380)
        at com.google.android.gms.maps.internal.IGoogleMapDelegate$zza$zza.addMarker(Unknown Source)
        at com.google.android.gms.maps.GoogleMap.addMarker(Unknown Source)
        at xxx.xxxxx.app.ui.details.DetailActivity.lambda$initGoogleMaps$23(DetailActivity.java:387)
        at xxx.xxxxx.app.ui.details.DetailActivity.access$lambda$10(DetailActivity.java)
        at xxx.xxxxx.app.ui.details.DetailActivity$$Lambda$13.onMapReady(Unknown Source)
        at com.google.android.gms.maps.SupportMapFragment$zza$1.zza(Unknown Source)
        at com.google.android.gms.maps.internal.zzl$zza.onTransact(Unknown Source)
        at android.os.Binder.transact(Binder.java:380)
        at com.google.android.gms.maps.internal.av.a(SourceFile:82)
        at com.google.maps.api.android.lib6.d.fa.run(Unknown Source)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5221)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
无论我如何检索drawable,尝试使用BitmapFactory.fromResources创建位图,然后使用BitmapDescritpionFactory.fromBitmap,但结果都是一样的。它只是无法处理矢量图形。也尝试了不同的向量,但似乎这里的问题不在于向量的复杂性。
有人知道如何修复这个崩溃吗?
@edit
看起来问题不在于BitmapDescriptior本身,而在于加载VectorDrawable时返回了不正确的位图。但是答案中提出的解决方案仍然是好的。

2
这是一个已知的问题。请参见https://code.google.com/p/gmaps-api-issues/issues/detail?id=9011 - vaughandroid
1
是的,但之前这里没有指向那个问题的链接,我认为这会很有用。当问题最终得到解决时,就会在那里明确说明。 - vaughandroid
8个回答

49

可能的解决方法:

private BitmapDescriptor getBitmapDescriptor(int id) {
    Drawable vectorDrawable = context.getDrawable(id);
    int h = ((int) Utils.convertDpToPixel(42, context));
    int w = ((int) Utils.convertDpToPixel(25, context));
    vectorDrawable.setBounds(0, 0, w, h);
    Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bm);
    vectorDrawable.draw(canvas);
    return BitmapDescriptorFactory.fromBitmap(bm);
}

11
可以使用 VectorDrawable.getIntrinsicHeight/Width(),而不是计算高度和宽度。除非你真的想要缩放图形。 - Sam G-H
实际上我正在进行缩放,但这可能对其他人有用。 - Than
9
如果您的年龄小于21岁,请使用Drawable vectorDrawable = ContextCompat.getDrawable(mContext, id);代替。 - Pepster

18
根据错误报告(感谢vaughandroid发布的问题!),目前不支持使用VectorDrawable。有关更多信息,请参见错误报告中的评论
以下是Google地图团队提供的建议解决方法:
/**
 * Demonstrates converting a {@link Drawable} to a {@link BitmapDescriptor},
 * for use as a marker icon.
 */
private BitmapDescriptor vectorToBitmap(@DrawableRes int id, @ColorInt int color) {
    Drawable vectorDrawable = ResourcesCompat.getDrawable(getResources(), id, null);
    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, color);
    vectorDrawable.draw(canvas);
    return BitmapDescriptorFactory.fromBitmap(bitmap);
}

这样使用:

// Vector drawable resource as a marker icon.
    mMap.addMarker(new MarkerOptions()
            .position(ALICE_SPRINGS)
            .icon(vectorToBitmap(R.drawable.ic_android, Color.parseColor("#A4C639")))
            .title("Alice Springs"));

对向量进行色彩调整是额外的奖励。


12

或者您可以简单地使用 Android KTX

val markerBitmap = ResourcesCompat.getDrawable(resources, R.drawable.ic_marker, null)?.toBitmap()
val icon = BitmapDescriptorFactory.fromBitmap(markerBitmap)
val marker = MarkerOptions().icon(icon)

参考文档:.toBitmap()


我们如何使用它改变向量的颜色/色调? - imin

6
这是另一个参考资料:

http://qiita.com/konifar/items/aaff934edbf44e39b04a

public class ResourceUtil {

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static Bitmap getBitmap(VectorDrawable vectorDrawable) {
    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());
    vectorDrawable.draw(canvas);
    return bitmap;
}

private static Bitmap getBitmap(VectorDrawableCompat vectorDrawable) {
    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());
    vectorDrawable.draw(canvas);
    return bitmap;
}

public static Bitmap getBitmap(Context context, @DrawableRes int drawableResId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableResId);
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else if (drawable instanceof VectorDrawableCompat) {
        return getBitmap((VectorDrawableCompat) drawable);
    } else if (drawable instanceof VectorDrawable) {
        return getBitmap((VectorDrawable) drawable);
    } else {
        throw new IllegalArgumentException("Unsupported drawable type");
    }
}
}

4

VectorDrawable 转换为没有色彩渲染的 BitmapDescriptor

private BitmapDescriptor getBitmapDescriptor(@DrawableRes int id) {
        Drawable vectorDrawable = ResourcesCompat.getDrawable(getResources(), id, null);
        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());
        vectorDrawable.draw(canvas);
        return BitmapDescriptorFactory.fromBitmap(bitmap);
    }

感谢@lbarbosa

3
相同的Kotlin
private fun getBitmapDescriptorFromVector(id: Int, context: Context): BitmapDescriptor {
    var vectorDrawable: Drawable = context.getDrawable(id)
    var h = (24 * getResources().getDisplayMetrics().density).toInt();
    var w = (24 * getResources().getDisplayMetrics().density).toInt();
    vectorDrawable.setBounds(0, 0, w, h)
    var bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    var canvas = Canvas(bm)
    vectorDrawable.draw(canvas)
    return BitmapDescriptorFactory.fromBitmap(bm)
}

编辑 @CoolMind,你是完全正确的,谢谢 - 已编辑


如果由于某种原因您使用可空的“context”,那么为什么要写:context?.getDrawable(id)!!?如果context == null,它将在Kotlin NPE中崩溃。 - CoolMind

3

Kotlin版本

private fun getBitmapDescriptor(context: Context, id: Int): BitmapDescriptor? {
    val vectorDrawable: Drawable?
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        vectorDrawable = context.getDrawable(id)
    } else {
        vectorDrawable = ContextCompat.getDrawable(context, id)
    }
    if (vectorDrawable != null) {
        val w = vectorDrawable.intrinsicWidth
        val h = vectorDrawable.intrinsicHeight

        vectorDrawable.setBounds(0, 0, w, h)
        val bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        val canvas = Canvas(bm);
        vectorDrawable.draw(canvas);

        return BitmapDescriptorFactory.fromBitmap(bm);
    }
    return null
}

我们如何使用这个来改变向量的颜色/色调? - imin

1

我认为这是最简单的解决方案,因为它将实现隐藏在MarkerOptions的扩展函数中:

fun MarkerOptions.icon(context: Context, @DrawableRes vectorDrawable: Int): MarkerOptions {
    this.icon(ContextCompat.getDrawable(context, vectorDrawable)?.run {
        setBounds(0, 0, intrinsicWidth, intrinsicHeight)
        val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
        draw(Canvas(bitmap))
        BitmapDescriptorFactory.fromBitmap(bitmap)
    })
    return this
}

当使用时,最终结果应该像这样:
MarkerOptions().position(myLocation).icon(requireContext(), R.drawable.ic_my_location_map)

如果您的应用程序中有多个位于不同位置的地图,则特别有用,因为您无需将实现复制/粘贴到多个类中。


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