38

我对我的 Android 应用程序有一个要求,即图形上的部分应该是可定制的,方法是从服务器端检索新的颜色和图像。其中一些图像是九个补丁图像。

我找不到创建和显示这些九个补丁图像(已通过网络检索到)的方法。

九个补丁图像被检索并作为位图保存在应用程序中。为了创建NinePatchDrawable,您需要相应的NinePatch或 NinePatch 的块 ( byte[])。NinePatch 不能从资源中加载,因为图像不存在于/res/drawable/. 此外,为了创建 NinePatch,您需要 NinePatch 的块。所以,这一切都深入到块中。
那么问题是,如何从现有位图(包含 NinePatch 信息)中格式化/生成块?

我搜索了 Android 源代码和网络,但似乎找不到任何示例。更糟糕的是,所有对 NinePatch 资源的解码似乎都是在本地完成的。

有没有人遇到过这种问题?

我的目标是 API 级别 4,如果这很重要的话。

4

7 回答 7

55

getNinePatchChunk works just fine. It returned null because you were giving Bitmap a "source" ninepatch. It needs a "compiled" ninepatch image.

There are two types of ninepatch file formats in the Android world ("source" and "compiled"). The source version is where you add the 1px transparency border everywhere-- when you compile your app into a .apk later, aapt will convert your *.9.png files to the binary format that Android expects. This is where the png file gets its "chunk" metadata. (read more)

Okay, now down to business (you're listening to DJ kanzure).

  1. Client code, something like this:

    InputStream stream = .. //whatever
    Bitmap bitmap = BitmapFactory.decodeStream(stream);
    byte[] chunk = bitmap.getNinePatchChunk();
    boolean result = NinePatch.isNinePatchChunk(chunk);
    NinePatchDrawable patchy = new NinePatchDrawable(bitmap, chunk, new Rect(), null);
    
  2. Server-side, you need to prepare your images. You can use the Android Binary Resource Compiler. This automates some of the pain away from creating a new Android project just to compile some *.9.png files into the Android native format. If you were to do this manually, you would essentially make a project and throw in some *.9.png files ("source" files), compile everything into the .apk format, unzip the .apk file, then find the *.9.png file, and that's the one you send to your clients.

Also: I don't know if BitmapFactory.decodeStream knows about the npTc chunk in these png files, so it may or may not be treating the image stream correctly. The existence of Bitmap.getNinePatchChunk suggests that BitmapFactory might-- you could go look it up in the upstream codebase.

In the event that it does not know about the npTc chunk and your images are being screwed up significantly, then my answer changes a little.

Instead of sending the compiled ninepatch images to the client, you write a quick Android app to load compiled images and spit out the byte[] chunk. Then, you transmit this byte array to your clients along with a regular image-- no transparent borders, not the "source" ninepatch image, not the "compiled" ninepatch image. You can directly use the chunk to create your object.

Another alternative is to use object serialization to send ninepatch images (NinePatch) to your clients, such as with JSON or the built-in serializer.

Edit If you really, really need to construct your own chunk byte array, I would start by looking at do_9patch, isNinePatchChunk, Res_png_9patch and Res_png_9patch::serialize() in ResourceTypes.cpp. There's also a home-made npTc chunk reader from Dmitry Skiba. I can't post links, so if someone can edit my answer that would be cool.

do_9patch: https://android.googlesource.com/platform/frameworks/base/+/gingerbread/tools/aapt/Images.cpp

isNinePatchChunk: http://netmite.com/android/mydroid/1.6/frameworks/base/core/jni/android/graphics/NinePatch.cpp

struct Res_png_9patch: https://scm.sipfoundry.org/rep/sipX/main/sipXmediaLib/contrib/android/android_2_0_headers/frameworks/base/include/utils/ResourceTypes.h

Dmitry Skiba stuff: http://code.google.com/p/android4me/source/browse/src/android/graphics/Bitmap.java

于 2011-04-01T23:02:41.590 回答
24

如果您需要即时创建 9Patches,请查看我制作的这个要点:https ://gist.github.com/4391807

您将任何位图传递给它,然后给它类似于 iOS 的 cap insets。

于 2012-12-27T20:49:29.620 回答
6

无需使用Android Binary Resource Compiler来准备编译好的9patch pngs,在android-sdk中使用aapt就可以了,命令行是这样的:
aapt.exe c -v -S /path/to/project -C /path/to/destination

于 2013-06-03T05:56:48.187 回答
6

我创建了一个工具来从(未编译的)NinePatch 位图创建 NinePatchDrawable。请参阅https://gist.github.com/knight9999/86bec38071a9e0a781ee

该方法 NinePatchDrawable createNinePatchDrawable(Resources res, Bitmap bitmap) 可以帮助您。

例如,

    ImageView imageView = (ImageView) findViewById(R.id.imageview);
    Bitmap bitmap = loadBitmapAsset("my_nine_patch_image.9.png", this);
    NinePatchDrawable drawable = NinePatchBitmapFactory.createNinePatchDrawable(getResources(), bitmap);
    imageView.setBackground( drawable );

在哪里

public static final Bitmap loadBitmapAsset(String fileName,Context context) {
    final AssetManager assetManager = context.getAssets();
    BufferedInputStream bis = null;
    try {
        bis = new BufferedInputStream(assetManager.open(fileName));
        return BitmapFactory.decodeStream(bis);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            bis.close();
        } catch (Exception e) {

        }
    }
    return null;
}

在本示例中,my_nine_patch_image.9.png位于 assets 目录下。

于 2015-04-23T13:00:14.683 回答
4

所以你基本上想创建一个NinePatchDrawable按需,不是吗?我尝试了以下代码,也许它对你有用:

InputStream in = getResources().openRawResource(R.raw.test);
Drawable d = NinePatchDrawable.createFromStream(in, null);
System.out.println(d.getMinimumHeight() + ":" + d.getMinimumHeight());

我认为这应该有效。您只需更改第一行即可从网络获取 InputStream。getNinePatchChunk()根据文档,不打算从开发人员那里调用,并且将来可能会中断。

于 2011-02-22T15:59:07.673 回答
3

工作和测试 - 运行时 NINEPATCH 创建

这是我对 android Ninepatch Builder 的实现,您可以通过这个类和下面的代码示例在运行时创建 NinePatches,方法是提供任何位图

public class NinePatchBuilder {
    int width,height;
    Bitmap bitmap;
    Resources resources;
    private ArrayList<Integer> xRegions=new ArrayList<Integer>();
    private ArrayList<Integer> yRegions=new ArrayList<Integer>();
    public NinePatchBuilder(Resources resources,Bitmap bitmap){
        width=bitmap.getWidth();
        height=bitmap.getHeight();
        this.bitmap=bitmap;
        this.resources=resources;
    }
    public NinePatchBuilder(int width, int height){
        this.width=width;
        this.height=height;
    }
    public NinePatchBuilder addXRegion(int x, int width){
        xRegions.add(x);
        xRegions.add(x+width);
        return this;
    }
    public NinePatchBuilder addXRegionPoints(int x1, int x2){
        xRegions.add(x1);
        xRegions.add(x2);
        return this;
    }
    public NinePatchBuilder addXRegion(float xPercent, float widthPercent){
        int xtmp=(int)(xPercent*this.width);
        xRegions.add(xtmp);
        xRegions.add(xtmp+(int)(widthPercent*this.width));
        return this;
    }
    public NinePatchBuilder addXRegionPoints(float x1Percent, float x2Percent){
        xRegions.add((int)(x1Percent*this.width));
        xRegions.add((int)(x2Percent*this.width));
        return this;
    }
    public NinePatchBuilder addXCenteredRegion(int width){
        int x=(int)((this.width-width)/2);
        xRegions.add(x);
        xRegions.add(x+width);
        return this;
    }
    public NinePatchBuilder addXCenteredRegion(float widthPercent){
        int width=(int)(widthPercent*this.width);
        int x=(int)((this.width-width)/2);
        xRegions.add(x);
        xRegions.add(x+width);
        return this;
    }
    public NinePatchBuilder addYRegion(int y, int height){
        yRegions.add(y);
        yRegions.add(y+height);
        return this;
    }
    public NinePatchBuilder addYRegionPoints(int y1, int y2){
        yRegions.add(y1);
        yRegions.add(y2);
        return this;
    }
    public NinePatchBuilder addYRegion(float yPercent, float heightPercent){
        int ytmp=(int)(yPercent*this.height);
        yRegions.add(ytmp);
        yRegions.add(ytmp+(int)(heightPercent*this.height));
        return this;
    }
    public NinePatchBuilder addYRegionPoints(float y1Percent, float y2Percent){
        yRegions.add((int)(y1Percent*this.height));
        yRegions.add((int)(y2Percent*this.height));
        return this;
    }
    public NinePatchBuilder addYCenteredRegion(int height){
        int y=(int)((this.height-height)/2);
        yRegions.add(y);
        yRegions.add(y+height);
        return this;
    }
    public NinePatchBuilder addYCenteredRegion(float heightPercent){
        int height=(int)(heightPercent*this.height);
        int y=(int)((this.height-height)/2);
        yRegions.add(y);
        yRegions.add(y+height);
        return this;
    }
    public byte[] buildChunk(){
        if(xRegions.size()==0){
            xRegions.add(0);
            xRegions.add(width);
        }
        if(yRegions.size()==0){
            yRegions.add(0);
            yRegions.add(height);
        }
        /* example code from a anwser above
        // The 9 patch segment is not a solid color.
        private static final int NO_COLOR = 0x00000001;
        ByteBuffer buffer = ByteBuffer.allocate(56).order(ByteOrder.nativeOrder());
        //was translated
        buffer.put((byte)0x01);
        //divx size
        buffer.put((byte)0x02);
        //divy size
        buffer.put((byte)0x02);
        //color size
        buffer.put(( byte)0x02);

        //skip
        buffer.putInt(0);
        buffer.putInt(0);

        //padding
        buffer.putInt(0);
        buffer.putInt(0);
        buffer.putInt(0);
        buffer.putInt(0);

        //skip 4 bytes
        buffer.putInt(0);

        buffer.putInt(left);
        buffer.putInt(right);
        buffer.putInt(top);
        buffer.putInt(bottom);
        buffer.putInt(NO_COLOR);
        buffer.putInt(NO_COLOR);

        return buffer;*/
        int NO_COLOR = 1;//0x00000001;
        int COLOR_SIZE=9;//could change, may be 2 or 6 or 15 - but has no effect on output 
        int arraySize=1+2+4+1+xRegions.size()+yRegions.size()+COLOR_SIZE;
        ByteBuffer byteBuffer=ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder());
        byteBuffer.put((byte) 1);//was translated
        byteBuffer.put((byte) xRegions.size());//divisions x
        byteBuffer.put((byte) yRegions.size());//divisions y
        byteBuffer.put((byte) COLOR_SIZE);//color size

        //skip
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);

        //padding -- always 0 -- left right top bottom
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);
        byteBuffer.putInt(0);

        //skip
        byteBuffer.putInt(0);

        for(int rx:xRegions)
            byteBuffer.putInt(rx); // regions left right left right ...
        for(int ry:yRegions)
            byteBuffer.putInt(ry);// regions top bottom top bottom ...

        for(int i=0;i<COLOR_SIZE;i++)
            byteBuffer.putInt(NO_COLOR);

        return byteBuffer.array();
    }
    public NinePatch buildNinePatch(){
        byte[] chunk=buildChunk();
        if(bitmap!=null)
            return new NinePatch(bitmap,chunk,null);
        return null;
    }
    public NinePatchDrawable build(){
        NinePatch ninePatch=buildNinePatch();
        if(ninePatch!=null)
            return new NinePatchDrawable(resources, ninePatch);
        return null;
    }
}

现在我们可以使用 Ninepatch builder 来创建 NinePatch 或 NinePatchDrawable 或者用于创建 NinePatch Chunk。

例子:

NinePatchBuilder builder=new NinePatchBuilder(getResources(), bitmap);
NinePatchDrawable drawable=builder.addXCenteredRegion(2).addYCenteredRegion(2).build();

//or add multiple patches

NinePatchBuilder builder=new NinePatchBuilder(getResources(), bitmap);
builder.addXRegion(30,2).addXRegion(50,1).addYRegion(20,4);
byte[] chunk=builder.buildChunk();
NinePatch ninepatch=builder.buildNinePatch();
NinePatchDrawable drawable=builder.build();

//Here if you don't want ninepatch and only want chunk use
NinePatchBuilder builder=new NinePatchBuilder(width, height);
byte[] chunk=builder.addXCenteredRegion(1).addYCenteredRegion(1).buildChunk();

只需将 NinePatchBuilder 类代码复制粘贴到 java 文件中,然后使用示例在应用程序运行时动态创建 NinePatch,任何分辨率。

于 2016-06-13T09:02:08.120 回答
0

Bitmap 类提供了一种方法来执行此操作yourbitmap.getNinePatchChunk()。我从未使用过它,但它似乎就是你要找的东西。

于 2011-02-22T15:46:07.347 回答