从具有多个路径标记的字符串创建矢量可绘制对象 (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 传递给 widthheight,并将 512.0f 作为 viewportWidthviewportHeight 输入变量。

我清理并修复了答案中的代码:

您现在可以传递具有不同颜色的路径列表并更改可绘制对象大小和视口大小。

问题出在以下行:

bb.putInt(0x44);  // Strings start

字符串池块的这个参数是块开始和字符串列表开始之间的字节数。在两者之间是字符串偏移列表,当有多个路径时它会更长,因此 0x44 值不再正确。