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

Is it possible to create a VectorDrawable from File System (*.xml File)

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

我目前正在尝试通过以下方式加载文件 Drawable.createFromPath(*Path to File*)VectorDrawable.createFromPath(*Path to File*),但我一直收到 NullPointer。 该文件存在并且是一个有效的 xml 文件(见下文)。

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

SkImageDecoder::Factory returned null

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

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

我还尝试了一些 public 文件夹,例如 "Downloads"。当我用 File.io Api 检查文件时,它 exists()canRead()canWrite()

更新这是从android开发页面

获取的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 源文件,否则不会。 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)因为膨胀调用试图将属性集转换为 XmlBlock.Parser,这会抛出 ClassCastExceptionsource 中记录了原因;它将“仅适用于已编译的 XML 文件”,例如 aapt 工具打包的资源。

实际上,是的,你可以,我已经在我自己的项目中做到了。

尽管如上一个答案所述,您确实需要 XML 可绘制对象的编译版本,而不是从 Android Studio 获得的版本。据我所知,VectorDrawable API 尚不支持从原始 XML.

加载

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

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

注意路径的 'assets/' 部分。这是必需的,afaik。

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

  1. 需要反思,所以有一天可能无法工作
  2. 反射是私有的API,不推荐
  3. 仅适用于已编译的 VectorDrawable XML 文件。

也就是说,也许您可​​以通过查看其他库的代码来创建自己的 XmlPullParser,例如 here (or my fork of it, here)。有趣的 class 名称是 "BinaryXmlParser",我认为这可能有所帮助。我尝试使用其他方法,但它导致了 java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser 的异常,所以它可能只想使用反射的 XmlBlock class,这意味着即使你实现了它,我认为也很有可能它不会工作。

这是代码(基于 ):

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