Search code examples
androiddrawablepaddingnine-patch

NinePatchDrawable does not get padding from chunk


I need help with NinePatchDrawable:

My app can download themes from the network. Almost all things work fine, except 9-Patch PNGs.

final Bitmap bubble = getFromTheme("bubble");
if (bubble == null) return null;

final byte[] chunk = bubble.getNinePatchChunk();
if (!NinePatch.isNinePatchChunk(chunk)) return null;

NinePatchDrawable d = new NinePatchDrawable(getResources(), bubble, chunk, new Rect(), null);
v.setBackgroundDrawable(d);

d = null;
System.gc();

getFromTheme() loads the Bitmap from the SD card. The 9-Patch PNGs are already compiled, that means they include the required chunk.

The way how I convert the Bitmap to a NinePatchDrawable object seems to be working, because the image is stretchable as well as I drew it.

The only thing that doesn't work is the padding. I already tried to set the padding to the view like this:

final Rect rect = new Rect();   // or just use the new Rect() set
d.getPadding(rect);             // in the constructor
v.setPadding(rect.left, rect.top, rect.right, rect.bottom);

d.getPadding(rect) should fill the variable rect with the padding got from the chunk, shouldn't it? But it doesn't.

Result: The TextView (v) does not show the text in the content area of the 9-Patch image. The paddings are set to 0 in each coordinate.

Thanks for reading.


Solution

  • Finally, I did it. Android wasn't interpreting the chunk data correctly. There might be bug. So you have to deserialize the chunk yourself to get the padding data.

    Here we go:

    package com.dragonwork.example;
    
    import android.graphics.Rect;
    
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    
    class NinePatchChunk {
    
        public static final int NO_COLOR = 0x00000001;
        public static final int TRANSPARENT_COLOR = 0x00000000;
    
        public final Rect mPaddings = new Rect();
    
        public int mDivX[];
        public int mDivY[];
        public int mColor[];
    
        private static void readIntArray(final int[] data, final ByteBuffer buffer) {
            for (int i = 0, n = data.length; i < n; ++i)
                data[i] = buffer.getInt();
        }
    
        private static void checkDivCount(final int length) {
            if (length == 0 || (length & 0x01) != 0)
                throw new RuntimeException("invalid nine-patch: " + length);
        }
    
        public static NinePatchChunk deserialize(final byte[] data) {
            final ByteBuffer byteBuffer =
                ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
    
            if (byteBuffer.get() == 0) return null; // is not serialized
    
            final NinePatchChunk chunk = new NinePatchChunk();
            chunk.mDivX = new int[byteBuffer.get()];
            chunk.mDivY = new int[byteBuffer.get()];
            chunk.mColor = new int[byteBuffer.get()];
    
            checkDivCount(chunk.mDivX.length);
            checkDivCount(chunk.mDivY.length);
    
            // skip 8 bytes
            byteBuffer.getInt();
            byteBuffer.getInt();
    
            chunk.mPaddings.left = byteBuffer.getInt();
            chunk.mPaddings.right = byteBuffer.getInt();
            chunk.mPaddings.top = byteBuffer.getInt();
            chunk.mPaddings.bottom = byteBuffer.getInt();
    
            // skip 4 bytes
            byteBuffer.getInt();
    
            readIntArray(chunk.mDivX, byteBuffer);
            readIntArray(chunk.mDivY, byteBuffer);
            readIntArray(chunk.mColor, byteBuffer);
    
            return chunk;
        }
    }
    

    Use the class above as following:

    final byte[] chunk = bitmap.getNinePatchChunk();
    if (NinePatch.isNinePatchChunk(chunk)) {
        textView.setBackgroundDrawable(new NinePatchDrawable(getResources(),
              bitmap, chunk, NinePatchChunk.deserialize(chunk).mPaddings, null));
    }
    

    And it will work perfectly!