9

我需要有关 NinePatchDrawable 的帮助:

我的应用程序可以从网络下载主题。除了 9-Patch PNG 之外,几乎所有东西都可以正常工作。

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() 从 SD 卡加载位图。9-Patch PNG 已经编译,这意味着它们包含所需的块。

我如何将位图转换为 NinePatchDrawable 对象的方式似乎很有效,因为图像是可拉伸的,就像我绘制它一样。

唯一不起作用的是填充。我已经尝试将填充设置为这样的视图:

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) 应该用从块中获得的填充填充变量 rect,不是吗?但事实并非如此。

结果:TextView (v) 未在 9-Patch 图像的内容区域中显示文本。每个坐标中的填充设置为 0。

谢谢阅读。

4

4 回答 4

17

最后,我做到了。Android 没有正确解释块数据。可能有bug。所以你必须自己反序列化块来获取填充数据。

开始了:

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

使用上面的类如下:

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

它会完美地工作!

于 2012-06-18T16:23:10.313 回答
4

它实际上比这稍微复杂一些,但归结为非常简单:

填充矩形由BitmapFactory.decodeStream(InputStream, Rect, Options). 没有decodeByteArray()哪个版本可以返回填充矩形。

整个九个补丁 API 有点傻:

  • decodeByteArray()调用nativeDecodeByteArray(),这可能比nativeDecodeStream()在 a 上更有效ByteArrayInputStream,但显然开发人员从未期望您要从内存中解码一个九个补丁。
  • padding rect由九个补丁使用,因此将其作为的一部分NinePatch而不是BitmapFactory. 可悲的是,NinePatch.java它只不过是一个将位图和九个补丁块传递给绘图方法的包装器(并且NinePatch.draw()由于对 的调用,大多数调用都不是线程安全的mRect.set(location))。
  • NinePatchDrawable不提供采用 aNinePatch和 padding rect 的方法,这NinePatch在应用程序代码中有些用处(除非您想自己进行填充)。没有NinePatchDrawable.getNinePatch()NinePatch.getBitmap()

这条评论总结得很好:

啊。约定是我们decodeStream已经分配了 pad rect,但是如果位图没有 Ninepatch 块,那么 pad 将被忽略。如果我们可以将其更改为懒惰地分配/分配 rect,我们可以避免生成 new Rect的 GC 搅动,只是将它们放在地板上。

我的修复相当简单:

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

未经测试,因为它已大大简化。特别是,您可能想要检查九个补丁块是否有效。

于 2013-04-05T18:24:24.260 回答
1

我从来没有见过这样的例子,其中Padding不包含在 9-patch 中:

在此处输入图像描述

为此,您应该首先构造一个NinePatch,然后从中创建您是 Drawable 的:

NinePatch ninePatch = new NinePatch(bitmap, chunk, srcName);
NinePatchDrawable d = new NinePatchDrawable(res, ninePatch);

但是,您似乎正在用一个空矩形构造您的 Drawable:

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

如果您想以编程方式指定填充,请尝试以下操作:

Rect paddingRectangle = new Rect(left, top, right, bottom);
NinePatchDrawable d = new NinePatchDrawable(getResources(), bubble, chunk, paddingRectangle, null);
于 2012-06-18T14:34:04.223 回答
1

聚会有点晚了,但这是我解决它的方法:

我使用NinePatchDrawable提供的解码器方法,它正确读取填充:

     var myDrawable = NinePatchDrawable.createFromStream(sr, null);
于 2015-10-22T14:21:57.153 回答