从文件系统(*.xml文件)创建VectorDrawable是否可行?

20

我正在尝试在我的Android应用中使用VectorDrawables。
我想要从文件系统加载一个xml文件,并获得一个android.graphics.Drawable实例来在ImageView中显示它。如果我将xml文件添加到资源目录中,它可以正常工作。但是,当我尝试从文件系统加载它时,我总是得到一个NullPointer。

我当前正在尝试使用Drawable.createFromPath(*文件路径*)VectorDrawable.createFromPath(*文件路径*)来加载文件,但我始终得到NullPointer。该文件存在且是一个有效的xml文件(见下文)。

在adb日志中,我总是得到:

SkImageDecoder::Factory returned null

当我使用mContext.getFilesDir()时,路径看起来像这样

/data/data/*packagename*/files/*filename*.xml

我还尝试了一些公共文件夹,比如“下载”文件夹。当我使用File.io API检查该文件时,它exists()canRead()canWrite()

更新 这是从Android Dev Pages中提取的XML代码。

 <vector xmlns:android="http://schemas.android.com/apk/res/android"
 android:height="64dp"
 android:width="64dp"
 android:viewportHeight="600"
 android:viewportWidth="600" >
 <group
     android:name="rotationGroup"
     android:pivotX="300.0"
     android:pivotY="300.0"
     android:rotation="45.0" >
     <path
         android:name="v"
         android:fillColor="#000000"
         android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
 </group>


请问您能否发布一下您XML文件的代码? - Kmeixner
3个回答

12

实际上你可以这样做,在我的项目中我也这样做了。

但是,如前面的回答所述,您需要使用编译后的XML可绘制版本,而不是从Android Studio等工具获取的版本。据我所知,VectorDrawable API目前还不支持从原始XML加载。

为此,只需将图像临时放置在res目录下,以便它们在构建过程中作为常规资源进行编译,然后找到生成的APK并从其中提取它们(您会注意到它们不再是文本文件,而是二进制文件)。现在将它们放回到您想要的任何位置下的资产目录中,并使用类似于以下代码的代码将它们加载为可绘制项:

XmlResourceParser parser = context.getAssets()
    .openXmlResourceParser("assets/folder/image.xml");
drawable = VectorDrawableCompat.createFromXml(context.getResources(), parser);

请注意路径中的 'assets/' 部分。据我所知,这是必需的。


有没有办法以我们在IDE中看到的普通XML文件的方式加载文件? - android developer
太棒了!我尝试了不同的方法大约两天,而你的方法成功了! :) - thorin86
真是个hack,虽然能用但担心这种方法是否存在兼容性问题。 - Brian Chu
我没有看到将图像移动到资产目录的结果。它仍然无法在运行时放置在那里,因此无法从文件系统中使用 :/ - Arkadiusz Cieśliński
3
能否从文件系统中完成这个操作呢?当我尝试解析“ic_launcher_foreground.xml”文件时,出现了“java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser”的错误。而这个文件是我放在文件系统中的。 - android developer

8
很遗憾,不行。除非以资源编译的方式编译XML源文件。VectorDrawable目前假定已编译图像源。

否则,您可能可以编写类似以下内容的代码:
VectorDrawable d = new VectorDrawable();
try( InputStream in = new FileInputStream( *Path to File* ); )
{
    XmlPullParser p = Xml.newPullParser();
    p.setInput( in, /*encoding, self detect*/null );
    d.inflate( getResources(), p, Xml.asAttributeSet(p) ); // FAILS
}

目前这种方法无法实现(API级别23,Marshmallow 6.0),因为inflate方法会将属性集合转换为XmlBlock.Parser,从而抛出ClassCastException异常。这个原因在源代码中有说明,它只能与已编译的XML文件一起工作,比如由aapt工具打包的资源。


2
已添加对此的请求:https://issuetracker.google.com/issues/62435069 - android developer
1
我尝试在 API 29 上运行这段代码,针对 "abc_vector_test.xml" 文件进行操作。这个文件是我从 APK 中提取出来的普通文件(意味着它不是 XML 文件而是二进制编译的文件)。但仍然会出现错误:"java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser"。这是为什么? - android developer

2

是的,这是可能的,但遗憾的是根据某人向我展示的内容,它有多个缺点:

  1. 需要反射,因此某天可能无法使用
  2. 反射作用于私有API,不建议使用
  3. 仅适用于已编译的VectorDrawable XML文件。

话虽如此,也许您可以通过查看其他库的代码(例如这里或者我的分支这里)来创建自己的XmlPullParser。那里有一个有趣的类名叫做“BinaryXmlParser”,我认为这可能会有所帮助。我尝试使用其他方法,但它引发了一个异常:java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser ,所以可能只想使用反射的XmlBlock类,这意味着即使您实现了它,我认为它也有很大的机会不能工作。

以下是代码(基于这里):

object VectorDrawableParser {
    /**
     * Create a vector drawable from a binary XML byte array.
     *
     * @param context Any context.
     * @param binXml  Byte array containing the binary XML.
     * @return The vector drawable or null it couldn't be created.
     */
    @SuppressLint("PrivateApi", "DiscouragedPrivateApi")
    fun getVectorDrawable(context: Context, binXml: ByteArray): Drawable? {
        try {
            // Get the binary XML parser (XmlBlock.Parser) and use it to create the drawable
            // This is the equivalent of what AssetManager#getXml() does
            val xmlBlock = Class.forName("android.content.res.XmlBlock")
            val xmlBlockConstr = xmlBlock.getConstructor(ByteArray::class.java)
            val xmlParserNew = xmlBlock.getDeclaredMethod("newParser")
            xmlBlockConstr.isAccessible = true
            xmlParserNew.isAccessible = true
            val parser = xmlParserNew.invoke(xmlBlockConstr.newInstance(binXml)) as XmlPullParser
            return if (Build.VERSION.SDK_INT >= 24) {
                Drawable.createFromXml(context.resources, parser)
            } else {
                // Before API 24, vector drawables aren't rendered correctly without compat lib
                val attrs = Xml.asAttributeSet(parser)
                var type = parser.next()
                while (type != XmlPullParser.START_TAG) {
                    type = parser.next()
                }
                VectorDrawableCompat.createFromXmlInner(context.resources, parser, attrs, null)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
}

如何使用:

val filePath = "/storage/emulated/0/abc_vector_test.xml"
val readBytes = FileInputStream(File(filePath)).readBytes()
val vectorDrawable = VectorDrawableParser.getVectorDrawable(this, readBytes)
Log.d("AppLog", "got it?${vectorDrawable != null}")

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