可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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!
回答2:
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()
calls nativeDecodeByteArray()
, which is presumably more efficient than nativeDecodeStream()
on a ByteArrayInputStream
, 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 of BitmapFactory
. Sadly, NinePatch.java
is not much more than a wrapper that passes the bitmap and nine-patch chunk to drawing methods (and most of the NinePatch.draw()
calls aren't thread-safe due to the call to mRect.set(location)
). NinePatchDrawable
doesn't offer a way to take a NinePatch
and a padding rect, which makes NinePatch
somewhat useless in application code (unless you want to do the padding yourself). There is no NinePatchDrawable.getNinePatch()
or NinePatch.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 new Rect
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.
回答3:
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);
回答4:
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);