是否可以从文件系统(*.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
,这会抛出 ClassCastException
。 source 中记录了原因;它将“仅适用于已编译的 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。
是的,这是可能的,但遗憾的是,根据某人向我展示的内容,它有多个缺点:
- 需要反思,所以有一天可能无法工作
- 反射是私有的API,不推荐
- 仅适用于已编译的 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}")
我正在尝试在我的 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
,这会抛出 ClassCastException
。 source 中记录了原因;它将“仅适用于已编译的 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。
是的,这是可能的,但遗憾的是,根据某人向我展示的内容,它有多个缺点:
- 需要反思,所以有一天可能无法工作
- 反射是私有的API,不推荐
- 仅适用于已编译的 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}")