从矢量图中获取位图

166
在我的应用程序中,我必须为通知设置一个大图标。 LargeIcon 必须是 Bitmap,而我的 drawable 是矢量图像(Android 中的新功能,请参见此链接)。 问题是当我尝试解码矢量图像资源时,返回 null。
下面是代码示例:
if (BitmapFactory.decodeResource(arg0.getResources(), R.drawable.vector_menu_objectifs) == null)
        Log.d("ISNULL", "NULL");
    else
        Log.d("ISNULL", "NOT NULL");
在这个示例中,当我将R.drawable.vector_menu_objectifs替换为“普通”图像,例如png时,结果不为空(我得到了正确的位图)。我是否漏掉了什么?
在这个示例中,如果我用“普通”的图片(例如png)替换R.drawable.vector_menu_objectifs,那么结果不为空(我可以得到正确的位图)。我是否忽略了什么?

1
曾经遇到类似的问题,没有解决方案,但有一个变通方法:https://dev59.com/SlwX5IYBdhLWcg3wpw5h#33550407 - Than
13个回答

282

已在API 17、21、23上进行验证。

public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
    Drawable drawable = ContextCompat.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;
}

更新:

工程的 gradle 文件:

dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0-alpha5'
    }

Gradle模块:

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'
    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        vectorDrawables.useSupportLibrary = true
    }
    ...
}
...

5
AppCompatDrawableManager 标记为 @RestrictTo(LIBRARY_GROUP),因此它是内部使用的,您不应该使用它(它的API可能会在没有通知的情况下更改)。请改用 ContextCompat - mradzinski
@mradzinski 太好了,它正在运行。我已经编辑了答案,以更好地反映最佳解决方案。 - Hugo Gresse
6
我有关于这个解决方案的问题。对我来说,使用AppCompatResources而不是ContextCompat解决了这个问题: Drawable drawable = AppCompatResources.getDrawable(context, drawableId); - Mike T
如果固有维度为-1怎么办? - John Glen
androidx.core.graphics.drawable 包中有一个扩展函数 toBitmap() - klenki
显示剩余2条评论

95

如果你愿意在Kotlin中使用Android KTX,你可以使用扩展方法Drawable#toBitmap()来实现与其他答案相同的效果:

val bitmap = AppCompatResources.getDrawable(requireContext(), drawableId).toBitmap() 
val bitmap = AppCompatResources.getDrawable(context, drawableId).toBitmap() 

要添加此及其他有用的扩展方法,您需要将以下内容添加到模块级别的build.gradle

repositories {
    google()
}

dependencies {
    implementation "androidx.core:core-ktx:1.2.0"
}

请查看此处获取将依赖项添加到您的项目的最新说明。

请注意,这适用于Drawable的任何子类,并且如果DrawableBitmapDrawable,则会快捷方式使用底层Bitmap


这是Kotlin中最简单的解决方案。 - AdamHurwitz
3
对我来说,使用VectorDrawable完全没有问题。 - ubuntudroid
这是最佳选择。默认的AndroidX提供了功能。 - Reshma

69

您可以使用以下方法:

@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(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else if (drawable instanceof VectorDrawable) {
        return getBitmap((VectorDrawable) drawable);
    } else {
        throw new IllegalArgumentException("unsupported drawable type");
    }
}

2
希望 @liltof 回来标记这个答案。需要注意的一点是,两个方法都需要 targetAPi 包装器 — 但是 Android Studio 会告诉你这一点。 - roberto tomás
1
我已经花了大约两天的时间尝试解决这个问题,一直认为是SVG文件的问题。谢谢! - El_o.di
1
在这一行 Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 上出现了 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.graphics.Bitmap.setHasAlpha(boolean)' on a null object reference 的错误。 - user25

30

基于之前的答案,可以简化为如下以匹配VectorDrawable和BitmapDrawable,并且兼容至少API 15。

public static Bitmap getBitmapFromDrawable(Context context, @DrawableRes int drawableId) {
    Drawable drawable = AppCompatResources.getDrawable(context, drawableId);

    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    } else if (drawable instanceof VectorDrawableCompat || drawable instanceof VectorDrawable) {
        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;
    } else {
        throw new IllegalArgumentException("unsupported drawable type");
    }
}

然后您需要在gradle文件中添加以下内容:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

在 Android 5.0以下的版本中,它将使用 VectorDrawableCompat,在 Android 5.0及以上的版本中,它将使用 VectorDrawable。

编辑

我已根据@user3109468的评论编辑了条件。

编辑2(2020年10月)

至少从API 21开始,您现在可以使用此代码而不是上面的代码(我没有在以前的API版本上尝试过):

AppCompatResources.getDrawable(context, R.drawable.your_drawable)

1
drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat 应该互换位置。当 VectorDrawable 不存在时会导致 Class Not Found,而应该首先检查 VectorDrawableCompat,因为它存在。 - Warrick
矢量图可视化对象的类型检查可以描述为(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable) - chmin.seo

7

致敬 @Alexey

这里是使用Context扩展的Kotlin版本

fun Context.getBitmapFromVectorDrawable(drawableId: Int): Bitmap? {
    var drawable = ContextCompat.getDrawable(this, drawableId) ?: return null

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable = DrawableCompat.wrap(drawable).mutate()
    }

    val bitmap = Bitmap.createBitmap(
            drawable.intrinsicWidth,
            drawable.intrinsicHeight,
            Bitmap.Config.ARGB_8888) ?: return null
    val canvas = Canvas(bitmap)
    drawable.setBounds(0, 0, canvas.width, canvas.height)
    drawable.draw(canvas)

    return bitmap
}

Activity 中的示例用法:

val bitmap = this.getBitmapFromVectorDrawable(R.drawable.ic_done_white_24dp)

1

在API 16 - JellyBean上测试,使用矢量图形

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
这里的矢量可绘制对象对我们有所帮助,但请记住,如果找不到或为空,则可能返回空值。
@Nullable
public static Bitmap drawableToBitmap(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    if (drawable != null) {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmp);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bmp;
    }
    return null;
}

1
使用以下代码将图像转换为具有正确长宽比(例如,用于通知图标):
public static Bitmap getBitmapFromVector(Context context, int drawableId) {
    Drawable drawable = ContextCompat.getDrawable(context, drawableId);
    int width = drawable.getIntrinsicWidth();
    int height = drawable.getIntrinsicHeight();
    Bitmap bitmap;
    if (width < height) {    //make a square
        bitmap = Bitmap.createBitmap(height, height, Bitmap.Config.ARGB_8888);
    } else {
        bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
    }
    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0,
            drawable.getIntrinsicWidth(),    //use dimensions of Drawable
            drawable.getIntrinsicHeight()
    );
    drawable.draw(canvas);
    return bitmap;
}

0
Drawable layerDrawable = (Drawable) imageBase.getDrawable();
Bitmap bitmap = Bitmap.createBitmap(layerDrawable.getIntrinsicWidth(),
        layerDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);  
imageTeste.setImageBitmap(addGradient(bitmap));

0
如果您想将输出按所需的大小进行缩放,请尝试以下代码片段:
fun getBitmapFromVectorDrawable(context: Context, drawableId: Int, outputSize: OutputSize? = null): Bitmap? {
    var drawable = ContextCompat.getDrawable(context, drawableId) ?: return null
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        drawable = DrawableCompat.wrap(drawable).mutate()
    }

    var targetBitmap: Bitmap
    if (outputSize != null) {
        targetBitmap = Bitmap.createBitmap(outputSize.width,
                outputSize.height, Bitmap.Config.ARGB_8888)
    } else {
        targetBitmap = Bitmap.createBitmap(drawable.intrinsicWidth,
            drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
    }

    val canvas = Canvas(targetBitmap)
    val scaleX =  targetBitmap.width.toFloat()/drawable.intrinsicWidth.toFloat()
    val scaleY =  targetBitmap.height.toFloat()/drawable.intrinsicHeight.toFloat()
    canvas.scale(scaleX, scaleY)
    drawable.draw(canvas)

    return targetBitmap
}

class OutputSize(val width: Int, val height: Int)

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