有没有办法从 API 获取 SVG 路径字符串并VectorDrawable动态创建?
我一直在尝试这样做几个小时而没有成功。更重要的是,Internet 上的所有 (!) 示例都解释了VectorDrawable从 XML 资源创建。
就我而言,XML 资源文件毫无意义,因为我试图从 Internet API 获取 SVG 路径。
有没有办法从 API 获取 SVG 路径字符串并VectorDrawable动态创建?
我一直在尝试这样做几个小时而没有成功。更重要的是,Internet 上的所有 (!) 示例都解释了VectorDrawable从 XML 资源创建。
就我而言,XML 资源文件毫无意义,因为我试图从 Internet API 获取 SVG 路径。
从 XML 文件中而不是从资源中扩充可绘制对象实际上是不可能的,因为可绘制对象将尝试将其XmlPullParser转换XmlResourceParser为仅由私有类实现的对象XmlBlock.Parser。甚至该解析器也仅用于解析二进制 XML 文件。我尝试了所有可能的方式来做到这一点而没有反思,这是不可能的。
因此,我找到了有关二进制 XML文件的文档并了解了它们是如何制作的,并帮助我处理了一些已编译的二进制 XML 矢量可绘制文件。该文档可以追溯到 2011 年并且仍然有效,我想它很可能会保持这种状态,因此未来的兼容性不是问题。
以前的版本经过一千多个路径的测试,没有问题。此处发布的新版本应该也可以正常工作。(以前的版本可以在答案历史中找到)与直接从资源加载drawable相比,我发现平均有14微秒左右的额外加载,并不明显。
这是代码:
public class VectorDrawableCreator {
private static final byte[][] BIN_XML_STRINGS = {
"width".getBytes(),
"height".getBytes(),
"viewportWidth".getBytes(),
"viewportHeight".getBytes(),
"fillColor".getBytes(),
"pathData".getBytes(),
"path".getBytes(),
"vector".getBytes(),
"http://schemas.android.com/apk/res/android".getBytes()
};
private static final int[] BIN_XML_ATTRS = {
android.R.attr.height,
android.R.attr.width,
android.R.attr.viewportWidth,
android.R.attr.viewportHeight,
android.R.attr.fillColor,
android.R.attr.pathData
};
private static final short CHUNK_TYPE_XML = 0x0003;
private static final short CHUNK_TYPE_STR_POOL = 0x0001;
private static final short CHUNK_TYPE_START_TAG = 0x0102;
private static final short CHUNK_TYPE_END_TAG = 0x0103;
private static final short CHUNK_TYPE_RES_MAP = 0x0180;
private static final short VALUE_TYPE_DIMENSION = 0x0500;
private static final short VALUE_TYPE_STRING = 0x0300;
private static final short VALUE_TYPE_COLOR = 0x1D00;
private static final short VALUE_TYPE_FLOAT = 0x0400;
/**
* Create a vector drawable from a list of paths and colors
* @param width drawable width
* @param height drawable height
* @param viewportWidth vector image width
* @param viewportHeight vector image height
* @param paths list of path data and colors
* @return the vector drawable or null it couldn't be created.
*/
public static Drawable getVectorDrawable(@NonNull Context context,
int width, int height,
float viewportWidth, float viewportHeight,
List<PathData> paths) {
byte[] binXml = createBinaryDrawableXml(width, height, viewportWidth, viewportHeight, paths);
try {
// Get the binary XML parser (XmlBlock.Parser) and use it to create the drawable
// This is the equivalent of what AssetManager#getXml() does
@SuppressLint("PrivateApi")
Class<?> xmlBlock = Class.forName("android.content.res.XmlBlock");
Constructor xmlBlockConstr = xmlBlock.getConstructor(byte[].class);
Method xmlParserNew = xmlBlock.getDeclaredMethod("newParser");
xmlBlockConstr.setAccessible(true);
xmlParserNew.setAccessible(true);
XmlPullParser parser = (XmlPullParser) xmlParserNew.invoke(
xmlBlockConstr.newInstance((Object) binXml));
if (Build.VERSION.SDK_INT >= 24) {
return Drawable.createFromXml(context.getResources(), parser);
} else {
// Before API 24, vector drawables aren't rendered correctly without compat lib
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type = parser.next();
while (type != XmlPullParser.START_TAG) {
type = parser.next();
}
return VectorDrawableCompat.createFromXmlInner(context.getResources(), parser, attrs, null);
}
} catch (Exception e) {
Log.e(VectorDrawableCreator.class.getSimpleName(), "Vector creation failed", e);
}
return null;
}
private static byte[] createBinaryDrawableXml(int width, int height,
float viewportWidth, float viewportHeight,
List<PathData> paths) {
List<byte[]> stringPool = new ArrayList<>(Arrays.asList(BIN_XML_STRINGS));
for (PathData path : paths) {
stringPool.add(path.data);
}
ByteBuffer bb = ByteBuffer.allocate(8192); // Capacity might have to be greater.
bb.order(ByteOrder.LITTLE_ENDIAN);
int posBefore;
// ==== XML chunk ====
// https://justanapplication.wordpress.com/2011/09/22/android-internals-binary-xml-part-two-the-xml-chunk/
bb.putShort(CHUNK_TYPE_XML); // Type
bb.putShort((short) 8); // Header size
int xmlSizePos = bb.position();
bb.position(bb.position() + 4);
// ==== String pool chunk ====
// https://justanapplication.wordpress.com/2011/09/15/android-internals-resources-part-four-the-stringpool-chunk/
int spStartPos = bb.position();
bb.putShort(CHUNK_TYPE_STR_POOL); // Type
bb.putShort((short) 28); // Header size
int spSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(stringPool.size()); // String count
bb.putInt(0); // Style count
bb.putInt(1 << 8); // Flags set: encoding is UTF-8
int spStringsStartPos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0); // Styles start
// String offsets
int offset = 0;
for (byte[] str : stringPool) {
bb.putInt(offset);
offset += str.length + (str.length > 127 ? 5 : 3);
}
posBefore = bb.position();
bb.putInt(spStringsStartPos, bb.position() - spStartPos);
bb.position(posBefore);
// String pool
for (byte[] str : stringPool) {
if (str.length > 127) {
byte high = (byte) ((str.length & 0xFF00 | 0x8000) >>> 8);
byte low = (byte) (str.length & 0xFF);
bb.put(high);
bb.put(low);
bb.put(high);
bb.put(low);
} else {
byte len = (byte) str.length;
bb.put(len);
bb.put(len);
}
bb.put(str);
bb.put((byte) 0);
}
if (bb.position() % 4 != 0) {
// Padding to align on 32-bit
bb.put(new byte[4 - (bb.position() % 4)]);
}
// Write string pool chunk size
posBefore = bb.position();
bb.putInt(spSizePos, bb.position() - spStartPos);
bb.position(posBefore);
// ==== Resource map chunk ====
// https://justanapplication.wordpress.com/2011/09/23/android-internals-binary-xml-part-four-the-xml-resource-map-chunk/
bb.putShort(CHUNK_TYPE_RES_MAP); // Type
bb.putShort((short) 8); // Header size
bb.putInt(8 + BIN_XML_ATTRS.length * 4); // Chunk size
for (int attr : BIN_XML_ATTRS) {
bb.putInt(attr);
}
// ==== Vector start tag ====
int vstStartPos = bb.position();
int vstSizePos = putStartTag(bb, 7, 4);
// Attributes
// android:width="24dp", value type: dimension (dp)
putAttribute(bb, 0, -1, VALUE_TYPE_DIMENSION, (width << 8) + 1);
// android:height="24dp", value type: dimension (dp)
putAttribute(bb, 1, -1, VALUE_TYPE_DIMENSION, (height << 8) + 1);
// android:viewportWidth="24", value type: float
putAttribute(bb, 2, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportWidth));
// android:viewportHeight="24", value type: float
putAttribute(bb, 3, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportHeight));
// Write vector start tag chunk size
posBefore = bb.position();
bb.putInt(vstSizePos, bb.position() - vstStartPos);
bb.position(posBefore);
for (int i = 0; i < paths.size(); i++) {
// ==== Path start tag ====
int pstStartPos = bb.position();
int pstSizePos = putStartTag(bb, 6, 2);
// android:fillColor="#aarrggbb", value type: #rgb.
putAttribute(bb, 4, -1, VALUE_TYPE_COLOR, paths.get(i).color);
// android:pathData="...", value type: string
putAttribute(bb, 5, 9 + i, VALUE_TYPE_STRING, 9 + i);
// Write path start tag chunk size
posBefore = bb.position();
bb.putInt(pstSizePos, bb.position() - pstStartPos);
bb.position(posBefore);
// ==== Path end tag ====
putEndTag(bb, 6);
}
// ==== Vector end tag ====
putEndTag(bb, 7);
// Write XML chunk size
posBefore = bb.position();
bb.putInt(xmlSizePos, bb.position());
bb.position(posBefore);
// Return binary XML byte array
byte[] binXml = new byte[bb.position()];
bb.rewind();
bb.get(binXml);
return binXml;
}
private static int putStartTag(ByteBuffer bb, int name, int attributeCount) {
// https://justanapplication.wordpress.com/2011/09/25/android-internals-binary-xml-part-six-the-xml-start-element-chunk/
bb.putShort(CHUNK_TYPE_START_TAG);
bb.putShort((short) 16); // Header size
int sizePos = bb.position();
bb.putInt(0); // Size, to be set later
bb.putInt(0); // Line number: None
bb.putInt(-1); // Comment: None
bb.putInt(-1); // Namespace: None
bb.putInt(name);
bb.putShort((short) 0x14); // Attributes start offset
bb.putShort((short) 0x14); // Attributes size
bb.putShort((short) attributeCount); // Attribute count
bb.putShort((short) 0); // ID attr: none
bb.putShort((short) 0); // Class attr: none
bb.putShort((short) 0); // Style attr: none
return sizePos;
}
private static void putEndTag(ByteBuffer bb, int name) {
// https://justanapplication.wordpress.com/2011/09/26/android-internals-binary-xml-part-seven-the-xml-end-element-chunk/
bb.putShort(CHUNK_TYPE_END_TAG);
bb.putShort((short) 16); // Header size
bb.putInt(24); // Chunk size
bb.putInt(0); // Line number: none
bb.putInt(-1); // Comment: none
bb.putInt(-1); // Namespace: none
bb.putInt(name); // Name: vector
}
private static void putAttribute(ByteBuffer bb, int name,
int rawValue, short valueType, int valueData) {
// https://justanapplication.wordpress.com/2011/09/19/android-internals-resources-part-eight-resource-entries-and-values/#struct_Res_value
bb.putInt(8); // Namespace index in string pool (always the android namespace)
bb.putInt(name);
bb.putInt(rawValue);
bb.putShort((short) 0x08); // Value size
bb.putShort(valueType);
bb.putInt(valueData);
}
public static class PathData {
public byte[] data;
public int color;
public PathData(byte[] data, int color) {
this.data = data;
this.color = color;
}
public PathData(String data, int color) {
this(data.getBytes(StandardCharsets.UTF_8), color);
}
}
}
调用从路径列表中getVectorDrawable返回 a 。VectorDrawabledrawable 可以包含多个不同颜色的路径。还有可绘制和视口大小的参数。
这是一个例子:
List<PathData> pathList = Arrays.asList(new PathData("M128.09 5.02a110.08 110.08 0 0 0-110 110h220a109.89 109.89 0 0 0-110-110z", Color.parseColor("#7cb342")),
new PathData("M128.09 115.02h-110a110.08 110.08 0 0 0 110 110 110.08 110.08 0 0 0 110-110z", Color.parseColor("#8bc34a")),
new PathData("M207.4 115.2v-.18h-5.1l-61.43-61.43h-25.48v20.6h-6.5a11.57 11.57 0 0 0-11.53 11.53v26.09h.11c-.11.9.5 2 1.7 3.32.12.08.12.08.12.2l3.96 4-46.11 79.91c5.33 4.5 11.04 8.4 17 11.8a109.81 109.81 0 0 0 108.04 0 110.04 110.04 0 0 0 51.52-64.65c.38-1.28.68-2.57 1.1-3.78z", Color.parseColor("#30000000")),
new PathData("M216.28 230.24a6.27 6.27 0 0 0-.9-2.8l-31.99-55.57-10.58-18.48-19.85-34.21-15.08 15.12 18.6 32.28 10.2 17.73 30.92 53.37a5.6 5.6 0 0 0 1.97 2.12l15.42 10.5c.6.39 1.29.39 1.9.08.6-.37.9-.98.9-1.7z", Color.parseColor("#e1e1e1")),
new PathData("M186.98 115.02a58.9 58.9 0 0 1-30.5 51.6 58.4 58.4 0 0 1-56.7 0l18.6-32.28-15.13-15.12-62.48 108.22c-.5.9-.8 1.78-.9 2.8l-1.4 18.6c-.12.71.3 1.28.9 1.7.6.37 1.29.3 1.9-.12l15.41-10.4a7.87 7.87 0 0 0 1.97-2.07l30.92-53.53a78.74 78.74 0 0 0 77.23 0 76.65 76.65 0 0 0 16.6-12.4 79.3 79.3 0 0 0 24.07-56.89z", Color.parseColor("#f1f1f1")),
new PathData("M147.3 74.12h-6.43v-20.6h-25.48v20.6h-6.5a11.57 11.57 0 0 0-11.53 11.5v26.07h.11c-.11 1.02.5 2.12 1.82 3.4l23.05 23.14a8.3 8.3 0 0 0 5.75 2.38v-.07l.07.07c2.12 0 4.2-.75 5.71-2.38l23.1-23.1c1.32-1.32 1.81-2.53 1.81-3.4h.12V85.7a11.68 11.68 0 0 0-11.6-11.6zm-19.14 40.9h-.07a15.4 15.4 0 0 1 0-30.8v-.2l.07.2a15.46 15.46 0 0 1 15.31 15.38 15.46 15.46 0 0 1-15.3 15.42z", Color.parseColor("#646464")));
