使用多个路径标签从字符串创建矢量可绘制对象(Android)

时间:2019-04-05 17:42:55

标签: android android-vectordrawable

我正在从服务器向应用程序发送矢量绘图。 我想实现一个函数,该函数获取路径数据和颜色的列表,并从给定的String / Color对列表中创建可绘制的向量。

我使用了类似线程(Create VectorDrawable from String (path)?上的Nicolas给出的答案,但是当给定多个路径/颜色对项时,它失败了。

有人可以帮助我找到错误吗?代码对我来说似乎还可以。

这是我的代码:

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;
}

它适用于向量中的单个路径,但是当我使用以下配对/颜色列表时,会出现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)
]

我为widthheight传递了512,并为512.0fviewportWidth输入变量传递了viewportHeight

1 个答案:

答案 0 :(得分:1)

我清理并修复了答案中的代码:https://stackoverflow.com/a/49920860/5288316

您现在可以传递具有不同颜色的路径列表,并更改可绘制尺寸和视口尺寸。

问题出在线路上

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

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