从具有多个路径标记的字符串创建矢量可绘制对象 (Android)
Create Vector Drawable from String with multiple path tags (Android)
我正在努力将矢量可绘制对象从服务器发送到我的应用程序。
我想实现一个函数,它获取路径数据和颜色的列表,并从给定的 String/Color 对列表中创建一个可绘制的矢量。
我使用了 Nicolas 在类似主题 (Create VectorDrawable from String (path)?) 上提供的答案,但是当给定多个 path/color 对项目时它失败了。
谁能帮我找出错误?代码对我来说似乎没问题。
这是我的代码:
private static final byte[][] BIN_XML_STRINGS = {
"height".getBytes(), "width".getBytes(), "viewportWidth".getBytes(),
"viewportHeight".getBytes(), "fillColor".getBytes(), "pathData".getBytes(),
"http://schemas.android.com/apk/res/android".getBytes(), "path".getBytes(), "vector".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};
public static Drawable getVectorDrawable(@NonNull Context context,
int width, int height,
float viewportWidth, float viewportHeight,
List<Pair<String, Integer>> pathColorList) {
List<Pair<byte[], Integer>> pathBytes = new ArrayList<>();
for(Pair<String, Integer> pathData: pathColorList){
pathBytes.add(new Pair<>(pathData.first.getBytes(), pathData.second));
}
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));
KappaLogger.LogError(parser.toString());
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) {
KappaExceptionUtils.sendStackTraceToLog(e);
}
return null;
}
private static byte[] createBinaryDrawableXml(int width, int height,
float viewportWidth, float viewportHeight,
List<Pair<byte[], Integer>> pathBytes) {
List<byte[]> binXmlStrings = new ArrayList<>(Arrays.asList(BIN_XML_STRINGS));
for(Pair<byte[], Integer> pathItem: pathBytes){
binXmlStrings.add(pathItem.first);
}
ByteBuffer bb = ByteBuffer.allocate(8192);
bb.order(ByteOrder.LITTLE_ENDIAN);
// ==== XML header ====
bb.putShort((short) 0x0003); // Type: XML
bb.putShort((short) 8); // Header size
int xmlSizePos = bb.position();
bb.position(bb.position() + 4);
// ==== String pool chunk ====
int spStartPos = bb.position();
bb.putShort((short) 0x0001); // Type: String pool
bb.putShort((short) 28); // Header size
int spSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(binXmlStrings.size()); // String count
bb.putInt(0); // Style count
bb.putInt(256); // Flags set: encoding is UTF-8
bb.putInt(0x44); // Strings start
bb.putInt(0); // Styles start
// String offsets
int offset = 0;
for (byte[] str : binXmlStrings) {
bb.putInt(offset);
offset += str.length + (str.length > 127 ? 5 : 3);
}
// String pool
for (byte[] str : binXmlStrings) {
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
int posBefore = bb.position();
bb.putInt(spSizePos, bb.position() - spStartPos);
bb.position(posBefore);
// ==== Resource map chunk ====
bb.putShort((short) 0x0180); // Type: Resource map
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();
bb.putShort((short) 0x0102); // Type: Start tag
bb.putShort((short) 16); // Header size
int vstSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0); // Line number: None
bb.putInt(-1); // Comment: None
bb.putInt(-1); // Namespace: None
bb.putInt(8); // Name: vector (index 9)
bb.putShort((short) 0x14);
bb.putShort((short) 0x14);
bb.putShort((short) 4); // Attribute count
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putShort((short) 0);
// Attributes
bb.putInt(6); // Namespace: android
bb.putInt(0); // Name: height
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0500); // value type: dimension
bb.putInt(height * 256 + 1); // Value data: 0x01 for dp, 0x18 for 24
bb.putInt(6); // Namespace: android
bb.putInt(1); // Name: width
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0500); // value type: dimension
bb.putInt(width * 256 + 1); // Value data: 0x01 for dp, 0x18 for 24
bb.putInt(6); // Namespace: android
bb.putInt(2); // Name: viewportWidth
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0400); // value type: float
bb.putInt(Float.floatToRawIntBits(viewportWidth)); // Value data: 24.0
bb.putInt(6); // Namespace: android
bb.putInt(3); // Name: viewportHeight
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0400); // value type: float
bb.putInt(Float.floatToRawIntBits(viewportHeight)); // Value data: 24.0
// Write vector start tag chunk size
posBefore = bb.position();
bb.putInt(vstSizePos, bb.position() - vstStartPos);
bb.position(posBefore);
for(int i=0; i<pathBytes.size(); i++){
// ==== Path start tag ====
int pstStartPos = bb.position();
bb.putShort((short) 0x0102); // Type: Start tag
bb.putShort((short) 16); // Header size
int pstSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0); // Line number: None
bb.putInt(-1); // Comment: None
bb.putInt(-1); // Namespace: None
bb.putInt(7); // Name: path (index 8)
bb.putShort((short) 0x14);
bb.putShort((short) 0x14);
bb.putShort((short) 2); // Attribute count
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putInt(6); // Namespace: android
bb.putInt(4); // Name: fillColor
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x1D00); // value type: color #rgb
bb.putInt(pathBytes.get(i).second); // Value data: color
bb.putInt(6); // Namespace: android
bb.putInt(5); // Name: pathData
bb.putInt(i+9); // Raw value: index 9 in string pool (path data)
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0300); // value type: string
bb.putInt(i+9); // Value data: same as raw data
// Write path start tag chunk size
posBefore = bb.position();
bb.putInt(pstSizePos, bb.position() - pstStartPos);
bb.position(posBefore);
// ==== Path end tag ====
bb.putShort((short) 0x0103);
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(7); // Name: path
}
// ==== Vector end tag ====
bb.putShort((short) 0x0103);
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(8); // Name: vector
// Write XML chunk size
posBefore = bb.position();
bb.putInt(xmlSizePos, bb.position());
bb.position(posBefore);
byte[] binXml = new byte[bb.position()];
bb.rewind();
bb.get(binXml);
StringBuilder sb = new StringBuilder();
for (byte b : binXml) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) sb.append('0');
sb.append(hex.toUpperCase());
sb.append(' ');
}
String str = sb.toString();
return binXml;
}
它适用于向量中的单个路径,但是当我使用以下 pair/color 列表时,出现 android.view.InflateException: Class not found h
错误。
输入:
[
new Pair<>("M177.230469 329.847656v167.382813c0 8.15625 6.613281 14.769531 14.769531 14.769531l9.855469-99.9375-9.855469-96.984375c-8.15625 0-14.769531 6.613281-14.769531 14.769531zm0 0",Color.BLACK),
new Pair<>("M192 315.078125v196.921875c8.15625 0 14.769531-6.613281 14.769531-14.769531v-167.382813c0-8.15625-6.613281-14.769531-14.769531-14.769531zm0 0", Color.RED)
]
我将 512 传递给 width
和 height
,并将 512.0f
作为 viewportWidth
和 viewportHeight
输入变量。
我清理并修复了答案中的代码:
您现在可以传递具有不同颜色的路径列表并更改可绘制对象大小和视口大小。
问题出在以下行:
bb.putInt(0x44); // Strings start
字符串池块的这个参数是块开始和字符串列表开始之间的字节数。在两者之间是字符串偏移列表,当有多个路径时它会更长,因此 0x44
值不再正确。
我正在努力将矢量可绘制对象从服务器发送到我的应用程序。 我想实现一个函数,它获取路径数据和颜色的列表,并从给定的 String/Color 对列表中创建一个可绘制的矢量。
我使用了 Nicolas 在类似主题 (Create VectorDrawable from String (path)?) 上提供的答案,但是当给定多个 path/color 对项目时它失败了。
谁能帮我找出错误?代码对我来说似乎没问题。
这是我的代码:
private static final byte[][] BIN_XML_STRINGS = {
"height".getBytes(), "width".getBytes(), "viewportWidth".getBytes(),
"viewportHeight".getBytes(), "fillColor".getBytes(), "pathData".getBytes(),
"http://schemas.android.com/apk/res/android".getBytes(), "path".getBytes(), "vector".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};
public static Drawable getVectorDrawable(@NonNull Context context,
int width, int height,
float viewportWidth, float viewportHeight,
List<Pair<String, Integer>> pathColorList) {
List<Pair<byte[], Integer>> pathBytes = new ArrayList<>();
for(Pair<String, Integer> pathData: pathColorList){
pathBytes.add(new Pair<>(pathData.first.getBytes(), pathData.second));
}
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));
KappaLogger.LogError(parser.toString());
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) {
KappaExceptionUtils.sendStackTraceToLog(e);
}
return null;
}
private static byte[] createBinaryDrawableXml(int width, int height,
float viewportWidth, float viewportHeight,
List<Pair<byte[], Integer>> pathBytes) {
List<byte[]> binXmlStrings = new ArrayList<>(Arrays.asList(BIN_XML_STRINGS));
for(Pair<byte[], Integer> pathItem: pathBytes){
binXmlStrings.add(pathItem.first);
}
ByteBuffer bb = ByteBuffer.allocate(8192);
bb.order(ByteOrder.LITTLE_ENDIAN);
// ==== XML header ====
bb.putShort((short) 0x0003); // Type: XML
bb.putShort((short) 8); // Header size
int xmlSizePos = bb.position();
bb.position(bb.position() + 4);
// ==== String pool chunk ====
int spStartPos = bb.position();
bb.putShort((short) 0x0001); // Type: String pool
bb.putShort((short) 28); // Header size
int spSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(binXmlStrings.size()); // String count
bb.putInt(0); // Style count
bb.putInt(256); // Flags set: encoding is UTF-8
bb.putInt(0x44); // Strings start
bb.putInt(0); // Styles start
// String offsets
int offset = 0;
for (byte[] str : binXmlStrings) {
bb.putInt(offset);
offset += str.length + (str.length > 127 ? 5 : 3);
}
// String pool
for (byte[] str : binXmlStrings) {
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
int posBefore = bb.position();
bb.putInt(spSizePos, bb.position() - spStartPos);
bb.position(posBefore);
// ==== Resource map chunk ====
bb.putShort((short) 0x0180); // Type: Resource map
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();
bb.putShort((short) 0x0102); // Type: Start tag
bb.putShort((short) 16); // Header size
int vstSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0); // Line number: None
bb.putInt(-1); // Comment: None
bb.putInt(-1); // Namespace: None
bb.putInt(8); // Name: vector (index 9)
bb.putShort((short) 0x14);
bb.putShort((short) 0x14);
bb.putShort((short) 4); // Attribute count
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putShort((short) 0);
// Attributes
bb.putInt(6); // Namespace: android
bb.putInt(0); // Name: height
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0500); // value type: dimension
bb.putInt(height * 256 + 1); // Value data: 0x01 for dp, 0x18 for 24
bb.putInt(6); // Namespace: android
bb.putInt(1); // Name: width
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0500); // value type: dimension
bb.putInt(width * 256 + 1); // Value data: 0x01 for dp, 0x18 for 24
bb.putInt(6); // Namespace: android
bb.putInt(2); // Name: viewportWidth
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0400); // value type: float
bb.putInt(Float.floatToRawIntBits(viewportWidth)); // Value data: 24.0
bb.putInt(6); // Namespace: android
bb.putInt(3); // Name: viewportHeight
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0400); // value type: float
bb.putInt(Float.floatToRawIntBits(viewportHeight)); // Value data: 24.0
// Write vector start tag chunk size
posBefore = bb.position();
bb.putInt(vstSizePos, bb.position() - vstStartPos);
bb.position(posBefore);
for(int i=0; i<pathBytes.size(); i++){
// ==== Path start tag ====
int pstStartPos = bb.position();
bb.putShort((short) 0x0102); // Type: Start tag
bb.putShort((short) 16); // Header size
int pstSizePos = bb.position();
bb.position(bb.position() + 4);
bb.putInt(0); // Line number: None
bb.putInt(-1); // Comment: None
bb.putInt(-1); // Namespace: None
bb.putInt(7); // Name: path (index 8)
bb.putShort((short) 0x14);
bb.putShort((short) 0x14);
bb.putShort((short) 2); // Attribute count
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putShort((short) 0);
bb.putInt(6); // Namespace: android
bb.putInt(4); // Name: fillColor
bb.putInt(-1); // Raw value: none
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x1D00); // value type: color #rgb
bb.putInt(pathBytes.get(i).second); // Value data: color
bb.putInt(6); // Namespace: android
bb.putInt(5); // Name: pathData
bb.putInt(i+9); // Raw value: index 9 in string pool (path data)
bb.putShort((short) 0x08); // Value size
bb.putShort((short) 0x0300); // value type: string
bb.putInt(i+9); // Value data: same as raw data
// Write path start tag chunk size
posBefore = bb.position();
bb.putInt(pstSizePos, bb.position() - pstStartPos);
bb.position(posBefore);
// ==== Path end tag ====
bb.putShort((short) 0x0103);
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(7); // Name: path
}
// ==== Vector end tag ====
bb.putShort((short) 0x0103);
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(8); // Name: vector
// Write XML chunk size
posBefore = bb.position();
bb.putInt(xmlSizePos, bb.position());
bb.position(posBefore);
byte[] binXml = new byte[bb.position()];
bb.rewind();
bb.get(binXml);
StringBuilder sb = new StringBuilder();
for (byte b : binXml) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) sb.append('0');
sb.append(hex.toUpperCase());
sb.append(' ');
}
String str = sb.toString();
return binXml;
}
它适用于向量中的单个路径,但是当我使用以下 pair/color 列表时,出现 android.view.InflateException: Class not found h
错误。
输入:
[
new Pair<>("M177.230469 329.847656v167.382813c0 8.15625 6.613281 14.769531 14.769531 14.769531l9.855469-99.9375-9.855469-96.984375c-8.15625 0-14.769531 6.613281-14.769531 14.769531zm0 0",Color.BLACK),
new Pair<>("M192 315.078125v196.921875c8.15625 0 14.769531-6.613281 14.769531-14.769531v-167.382813c0-8.15625-6.613281-14.769531-14.769531-14.769531zm0 0", Color.RED)
]
我将 512 传递给 width
和 height
,并将 512.0f
作为 viewportWidth
和 viewportHeight
输入变量。
我清理并修复了答案中的代码:
您现在可以传递具有不同颜色的路径列表并更改可绘制对象大小和视口大小。
问题出在以下行:
bb.putInt(0x44); // Strings start
字符串池块的这个参数是块开始和字符串列表开始之间的字节数。在两者之间是字符串偏移列表,当有多个路径时它会更长,因此 0x44
值不再正确。