在运行时创build一个NinePatch / NinePatchDrawable

我对Android应用程序有一个要求,即通过从服务器端检索新的颜色和图像,graphics上的部分应该可以自定义。 其中一些图像是九块图像。

我找不到一种方法来创build和显示这些九个补丁的图像(已经通过networking检索)。

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

我已经通过Android源代码和Websearch,我似乎无法find任何这样的例子。 更糟糕的是,NinePatch资源的所有解码似乎都是在本地完成的。

有没有人有这种问题的经验?

如果这一点很重要,我将针对API级别4。

getNinePatchChunk工作得很好。 它返回null,因为你给Bitmap一个“源”ninepatch。 它需要一个“编译的”ninepatch图像。

Android世界中有两种types的ninepatch文件格式(“源”和“编译”)。 源代码版本是您无处不在地添加1px透明边框的地方 – 当您将应用程序编译为.apk文件后,aapt会将您的* .9.png文件转换为Android预期的二进制格式。 这是png文件获取“块”元数据的地方。 ( 阅读更多 )

好吧,现在正要做生意(你正在听DJ kanzure)。

  1. 客户端代码,如下所示:

     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. 服务器端,你需要准备你的图像。 您可以使用Android二进制资源编译器 。 这将自动创build一个新的Android项目的一些痛苦,只是将一些* .9.png文件编译成Android本机格式。 如果你想手动完成这个工作,你可能会做一个项目,然后抛出一些* .9.png文件(“源文件”),将所有东西编译成.apk格式,解压缩.apk文件,然后find*。 9.png文件,这是你发送给你的客户。

另外:我不知道BitmapFactory.decodeStream知道这些PNG文件中的npTc块,所以它可能会或可能不会正确处理图像stream。 Bitmap.getNinePatchChunk的存在意味着BitmapFactory可能会在上游代码库中查找它。

如果它不知道npTc块和您的图像被大大搞砸,那么我的答案会有所改变。

而不是发送编译的九张图片到客户端,你写一个快速的Android应用程序来加载编译的图像和吐出byte[]块。 然后,将这个字节数组和一个普通的图像一起传输给客户端 – 没有透明边框,而不是“源”ninepatch图像,而不是“编译的”ninepatch图像。 你可以直接使用这个块创build你的对象。

另一种方法是使用对象序列化将ninepatch图像( NinePatch )发送到客户端,例如使用JSON或内置序列化程序。

编辑如果你确实需要构build你自己的块字节数组,我将首先查看do_9patchisNinePatchChunkRes_png_9patchRes_png_9patch::serialize() 。 还有一个来自Dmitry Skiba的自制npTc大块阅读器。 我无法发布链接,所以如果有人可以编辑我的答案,这将是很酷的。

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

德米特里Skiba的东西: http : //code.google.com/p/android4me/source/browse/src/android/graphics/Bitmap.java

如果你需要创build9Patches在飞行检查出这个要点我: https ://gist.github.com/4391807

你把它传递给任何位图,然后给它与iOS类似的cap insets。

无需使用Android二进制资源编译器来编译9patch png,只需在android-sdk中使用aapt即可,命令行如下所示:
aapt.exe c -v -S /path/to/project -C /path/to/destination

我创build了一个从(未编译的)NinePatch位图创buildNinePatchDrawable的工具。 见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位于资产目录下。

所以你基本上要按需创build一个NinePatchDrawable ,不是吗? 我试了下面的代码,也许它适合你:

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

我认为这应该工作。 你只需要改变第一行,从网上获取InputStream。 getNinePatchChunk()不打算根据文档从开发人员调用,并可能在未来打破。

工作和testing – RUNTIME NINEPATCH创build

这是我的android的Ninepatch Builder的实现,你可以在Runtime上通过这个类创buildNinePatches并且通过下面的代码实例提供任何的Bitmap

 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.getWidth(); 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来创buildNinePatch或NinePatchDrawable,或者创buildNinePatch 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文件中,然后使用示例在应用程序运行时,使用任何分辨率即时创buildNinePatch。

Bitmap类提供了一个方法来做到这一点yourbitmap.getNinePatchChunk() 。 我从来没有使用它,但它看起来像那是你在找什么。