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.
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!
It's actually slightly more complicated than that, but what it boils down to is pretty simple:
The padding rect is returned by BitmapFactory.decodeStream(InputStream, Rect, Options)
. There is no version of decodeByteArray()
which can return the padding rect.
The whole nine-patch API is a bit silly:
decodeByteArray()
callsnativeDecodeByteArray()
, which is presumably more efficient thannativeDecodeStream()
on aByteArrayInputStream
, but obviously the devs never expected you to want to decode a nine-patch from memory.- The padding rect is only used by nine-patches, so it makes more sense for it to be part of
NinePatch
instead ofBitmapFactory
. Sadly,NinePatch.java
is not much more than a wrapper that passes the bitmap and nine-patch chunk to drawing methods (and most of theNinePatch.draw()
calls aren't thread-safe due to the call tomRect.set(location)
). NinePatchDrawable
doesn't offer a way to take aNinePatch
and a padding rect, which makesNinePatch
somewhat useless in application code (unless you want to do the padding yourself). There is noNinePatchDrawable.getNinePatch()
orNinePatch.getBitmap()
.
This comment sums it up pretty well:
ugh. The
decodeStream
contract is that we have already allocated the pad rect, but if the bitmap does not had a ninepatch chunk, then the pad will be ignored. If we could change this to lazily alloc/assign the rect, we could avoid the GC churn of making newRect
s only to drop them on the floor.
My fix is fairly simple:
public final class NinePatchWrapper {
private final Bitmap mBitmap;
private final Rect mPadding;
/**
* The caller must ensure that that bitmap and padding are not modified after
* this method returns. We could copy them, but Bitmap.createBitmap(Bitmap)
* does not copy the nine-patch chunk on some Android versions.
*/
public NinePatchWrapper(Bitmap bitmap, Rect padding) {
mBitmap = bitmap;
mPadding = padding;
}
public NinePatchDrawable newDrawable(Resources resources) {
return new NinePatchDrawable(mBitmap, mBitmap.getNinePatchChunk(), mPadding, null);
}
}
...
public NinePatchWrapper decodeNinePatch(byte[] byteArray, int density) {
Rect padding = new Rect();
ByteArrayInputStream stream = new ByteArrayInputStream(byteArray);
Bitmap bitmap = BitmapFactory.decodeStream(stream, padding, null);
bitmap.setDensity(density);
return new NinePatchWrapper(bitmap, padding);
}
Untested, since it's greatly simplified. In particular, you might want to check that the nine-patch chunk is valid.
I've never seen an example where the Padding
isn't included as part of the 9-patch like so:
To do this you should first construct a NinePatch and then create you're Drawable from it:
NinePatch ninePatch = new NinePatch(bitmap, chunk, srcName);
NinePatchDrawable d = new NinePatchDrawable(res, ninePatch);
However, you seem to be constructing your Drawable with an empty rectangle:
NinePatchDrawable d = new NinePatchDrawable(getResources(), bubble, chunk, new Rect(), null);
If you want to programatically specify the padding try this:
Rect paddingRectangle = new Rect(left, top, right, bottom);
NinePatchDrawable d = new NinePatchDrawable(getResources(), bubble, chunk, paddingRectangle, null);
A bit late to the party, but here is how I solved it:
I use the decoder method that NinePatchDrawable
provides, it reads the padding correctly:
var myDrawable = NinePatchDrawable.createFromStream(sr, null);
来源:https://stackoverflow.com/questions/11065996/ninepatchdrawable-does-not-get-padding-from-chunk