Android: compiling 9-patch files to be used outside of the drawable folder?

前端 未结 2 1948
野趣味
野趣味 2021-01-05 20:18

I have a need to load 9-patch files from outside of the drawable folder. This is so that my application can download new skins from a server, for example. I have discovered

相关标签:
2条回答
  • 2021-01-05 20:59

    There is no simple way to do this afaik. 9-patch compilation is done by aapt and is rather simple: it discards the black borders and encodes their content inside a PNG chunk. It would be fairly trivial for you to write a tool that does something similar. Note that you don't even need to use the same format. If you look at the various NinePatch APIs in the doc, you will see that you can submit your own "chunk" (which encodes the stretch regions and padding.) The structure of a chunk byte[] array is explained here:

    /**
     * This chunk specifies how to split an image into segments for
     * scaling.
     *
     * There are J horizontal and K vertical segments.  These segments divide
     * the image into J*K regions as follows (where J=4 and K=3):
     *
     *      F0   S0    F1     S1
     *   +-----+----+------+-------+
     * S2|  0  |  1 |  2   |   3   |
     *   +-----+----+------+-------+
     *   |     |    |      |       |
     *   |     |    |      |       |
     * F2|  4  |  5 |  6   |   7   |
     *   |     |    |      |       |
     *   |     |    |      |       |
     *   +-----+----+------+-------+
     * S3|  8  |  9 |  10  |   11  |
     *   +-----+----+------+-------+
     *
     * Each horizontal and vertical segment is considered to by either
     * stretchable (marked by the Sx labels) or fixed (marked by the Fy
     * labels), in the horizontal or vertical axis, respectively. In the
     * above example, the first is horizontal segment (F0) is fixed, the
     * next is stretchable and then they continue to alternate. Note that
     * the segment list for each axis can begin or end with a stretchable
     * or fixed segment.
     *
     * The relative sizes of the stretchy segments indicates the relative
     * amount of stretchiness of the regions bordered by the segments.  For
     * example, regions 3, 7 and 11 above will take up more horizontal space
     * than regions 1, 5 and 9 since the horizontal segment associated with
     * the first set of regions is larger than the other set of regions.  The
     * ratios of the amount of horizontal (or vertical) space taken by any
     * two stretchable slices is exactly the ratio of their corresponding
     * segment lengths.
     *
     * xDivs and yDivs point to arrays of horizontal and vertical pixel
     * indices.  The first pair of Divs (in either array) indicate the
     * starting and ending points of the first stretchable segment in that
     * axis. The next pair specifies the next stretchable segment, etc. So
     * in the above example xDiv[0] and xDiv[1] specify the horizontal
     * coordinates for the regions labeled 1, 5 and 9.  xDiv[2] and
     * xDiv[3] specify the coordinates for regions 3, 7 and 11. Note that
     * the leftmost slices always start at x=0 and the rightmost slices
     * always end at the end of the image. So, for example, the regions 0,
     * 4 and 8 (which are fixed along the X axis) start at x value 0 and
     * go to xDiv[0] and slices 2, 6 and 10 start at xDiv[1] and end at
     * xDiv[2].
     *
     * The array pointed to by the colors field lists contains hints for
     * each of the regions.  They are ordered according left-to-right and
     * top-to-bottom as indicated above. For each segment that is a solid
     * color the array entry will contain that color value; otherwise it
     * will contain NO_COLOR.  Segments that are completely transparent
     * will always have the value TRANSPARENT_COLOR.
     *
     * The PNG chunk type is "npTc".
     */
    struct Res_png_9patch
    {
        Res_png_9patch() : wasDeserialized(false), xDivs(NULL),
                           yDivs(NULL), colors(NULL) { }
    
        int8_t wasDeserialized;
        int8_t numXDivs;
        int8_t numYDivs;
        int8_t numColors;
    
        // These tell where the next section of a patch starts.
        // For example, the first patch includes the pixels from
        // 0 to xDivs[0]-1 and the second patch includes the pixels
        // from xDivs[0] to xDivs[1]-1.
        // Note: allocation/free of these pointers is left to the caller.
        int32_t* xDivs;
        int32_t* yDivs;
    
        int32_t paddingLeft, paddingRight;
        int32_t paddingTop, paddingBottom;
    
        enum {
            // The 9 patch segment is not a solid color.
            NO_COLOR = 0x00000001,
    
            // The 9 patch segment is completely transparent.
            TRANSPARENT_COLOR = 0x00000000
        };
        // Note: allocation/free of this pointer is left to the caller.
        uint32_t* colors;
    
        // Convert data from device representation to PNG file representation.
        void deviceToFile();
        // Convert data from PNG file representation to device representation.
        void fileToDevice();
        // Serialize/Marshall the patch data into a newly malloc-ed block
        void* serialize();
        // Serialize/Marshall the patch data
        void serialize(void* outData);
        // Deserialize/Unmarshall the patch data
        static Res_png_9patch* deserialize(const void* data);
        // Compute the size of the serialized data structure
        size_t serializedSize();
    };
    
    0 讨论(0)
  • 2021-01-05 21:16

    Here is a workaround which works in my case.

    I have default 9-patch images in my app which I use as message bubbles. I wanted to create downloadable themes which changes the bubbles and other stuff like font colors, etc.

    The *.9.png images included in the drawable folders are the default, they include the black pixels around the image:

    default_outgoing.9.png

    The "custom"/themed bubbles have exactly the same dimensions, placement with minor changes which still uses the same "chunk" area as the default, but these don't include the black pixels or the .9 in the file name:

    pink_round_outgoing.png

    So how does the custom one work and still look fine? Here is a bit of code that fetches the custom image, gets some values from the default 9-patch image and applies them to the custom bitmap:

    //Get the custom replacement image
    Bitmap bitmap = BitmapFactory.decodeFile(folderpath + File.separator + "pink_round_outgoing.png");
    
    //Get padding from the default 9-patch drawable
    Drawable existingDrawable = ContextCompat.getDrawable(this, R.drawable.default_outgoing);
    Rect padding = new Rect();
    if (existingDrawable != null) {
        existingDrawable.getPadding(padding);
    }
    
    //Get 9-patch chunk from the default 9-patch drawable
    Bitmap existingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_outgoing);
    byte[] chunk = existingBitmap.getNinePatchChunk();
    
    //Finally create your custom 9-Patch drawable and set it to background
    NinePatchDrawable d = new NinePatchDrawable(getResources(), bitmap, chunk, padding, null);
    view.setBackground(d);
    
    0 讨论(0)
提交回复
热议问题