To inflate rendering from an XML file, and not from resources, is actually impossible, because rendering will try to lead XmlPullParser
to XmlResourceParser
which is implemented only by a closed class XmlBlock.Parser
. Even this parser is only used to parse binary XML files. I tried all possible ways to do this without thinking, this is impossible.
So I found the documentation for the binary XML files and found out how they were created, helped with some of the compiled binary files of the vector XML files that I had. The documentation dates from 2011 and is still valid, I think that most likely it will remain so compatibility in the future will not be a problem.
. , , . ( ). , , 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;
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 {
@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 {
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);
bb.order(ByteOrder.LITTLE_ENDIAN);
int posBefore;
bb.putShort(CHUNK_TYPE_XML);
bb.putShort((short) 8);
int xmlSizePos = bb.position();
bb.position(bb.position() + 4);
int spStartPos = bb.position();
bb.putShort(CHUNK_TYPE_STR_POOL);
bb.putShort((short) 28);
int spSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(stringPool.size());
bb.putInt(0);
bb.putInt(1 << 8);
int spStringsStartPos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0);
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);
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) {
bb.put(new byte[4 - (bb.position() % 4)]);
}
posBefore = bb.position();
bb.putInt(spSizePos, bb.position() - spStartPos);
bb.position(posBefore);
bb.putShort(CHUNK_TYPE_RES_MAP);
bb.putShort((short) 8);
bb.putInt(8 + BIN_XML_ATTRS.length * 4);
for (int attr : BIN_XML_ATTRS) {
bb.putInt(attr);
}
int vstStartPos = bb.position();
int vstSizePos = putStartTag(bb, 7, 4);
putAttribute(bb, 0, -1, VALUE_TYPE_DIMENSION, (width << 8) + 1);
putAttribute(bb, 1, -1, VALUE_TYPE_DIMENSION, (height << 8) + 1);
putAttribute(bb, 2, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportWidth));
putAttribute(bb, 3, -1, VALUE_TYPE_FLOAT, Float.floatToRawIntBits(viewportHeight));
posBefore = bb.position();
bb.putInt(vstSizePos, bb.position() - vstStartPos);
bb.position(posBefore);
for (int i = 0; i < paths.size(); i++) {
int pstStartPos = bb.position();
int pstSizePos = putStartTag(bb, 6, 2);
putAttribute(bb, 4, -1, VALUE_TYPE_COLOR, paths.get(i).color);
putAttribute(bb, 5, 9 + i, VALUE_TYPE_STRING, 9 + i);
posBefore = bb.position();
bb.putInt(pstSizePos, bb.position() - pstStartPos);
bb.position(posBefore);
putEndTag(bb, 6);
}
putEndTag(bb, 7);
posBefore = bb.position();
bb.putInt(xmlSizePos, bb.position());
bb.position(posBefore);
byte[] binXml = new byte[bb.position()];
bb.rewind();
bb.get(binXml);
return binXml;
}
private static int putStartTag(ByteBuffer bb, int name, int attributeCount) {
bb.putShort(CHUNK_TYPE_START_TAG);
bb.putShort((short) 16);
int sizePos = bb.position();
bb.putInt(0);
bb.putInt(0);
bb.putInt(-1);
bb.putInt(-1);
bb.putInt(name);
bb.putShort((short) 0x14);
bb.putShort((short) 0x14);
bb.putShort((short) attributeCount);
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putShort((short) 0);
return sizePos;
}
private static void putEndTag(ByteBuffer bb, int name) {
bb.putShort(CHUNK_TYPE_END_TAG);
bb.putShort((short) 16);
bb.putInt(24);
bb.putInt(0);
bb.putInt(-1);
bb.putInt(-1);
bb.putInt(name);
}
private static void putAttribute(ByteBuffer bb, int name,
int rawValue, short valueType, int valueData) {
bb.putInt(8);
bb.putInt(name);
bb.putInt(rawValue);
bb.putShort((short) 0x08);
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
VectorDrawable
. Drawable . .
:
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")));