2011-02-22 56 views
36

我對我的Android應用程序有一個要求,即圖形上的部分應該可以通過從服務器端檢索新顏色和圖像進行自定義。其中一些圖像是九幅圖像。在運行時創建NinePatch/NinePatchDrawable

我找不到一種方法來創建和顯示這些九個補丁的圖像(已通過網絡檢索到)。

九個補丁圖像被檢索並保存在應用程序中作爲位圖。要創建NinePatchDrawable,您需要相應的NinePatch或NinePatch的塊(byte[])。 NinePatch無法從資源中加載,因爲圖像不存在於/res/drawable/中。此外,爲了創建NinePatch,你需要NinePatch的塊。所以,這一切都深入到大塊。
問題是,如何格式/從現有的位圖(包含NinePatch信息)生成塊?

我已經通過Android源代碼和網絡搜索,我似乎無法找到任何這樣的例子。更糟糕的是,NinePatch資源的所有解碼似乎都是在本地完成的。

有沒有人有過這種問題的經驗?

我針對API級別4,如果這是重要的。

回答

0

Bitmap類提供了一個方法來做到這一點yourbitmap.getNinePatchChunk()。我從來沒有用過它,但它看起來像你想要的。

+0

正如mreichelt所說,文檔說_「返回一個可選的私有數據數組,由UI系統用於某些位圖,不打算由應用程序調用。所以這種方法不能使用。不管怎麼說,還是要謝謝你! – jacob11

4

所以你基本上想要創建一個NinePatchDrawable點播,不是嗎?我想下面的代碼,也許對你有用:

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

我認爲這應該工作。您只需更改第一行以從Web獲取InputStream。 getNinePatchChunk()不打算從開發人員根據文檔調用,並可能在未來打破。

+0

這是糾正我的錯誤,我只是看着方法概述,他們離開了。 – FoamyGuy

+0

謝謝你的回覆,mreichelt! – jacob11

+2

是的,我想創建一個'NinePatchDrawable'。您是否真的使用過上面的代碼在目標設備上創建了「NinePatchDrawable」?我試過了,'Drawable.createFromStream(InputStream,String)'返回一個'BitmapDrawable'(擴展'Drawable')而不是'NinePatchDrawable'(擴展'Drawable')。我已經用遠程圖像的輸入流(通過HTTP)和輸入流對本地圖像文件進行了嘗試。在這兩種情況下都會返回一個'BitmapDrawable'。我已經在三星Galaxy S和HTC Desire上測試過這兩款產品(都運行2.2版本)。任何其他建議!?任何幫助表示讚賞! – jacob11

55

getNinePatchChunk工作得很好。它返回null,因爲你給Bitmap一個「源」ninepatch。它需要一個「編譯的」ninepatch圖像。

Android世界中有兩種類型的ninepatch文件格式(「源」和「編譯」)。源版本是您無處不在地添加1px透明邊框的地方 - 當您將應用程序編譯爲.apk文件後,aapt會將您的* .9.png文件轉換爲Android預期的二進制格式。這是png文件獲取其「塊」元數據的地方。 (read more

好吧,現在到商業(你正在聽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 Binary Resource Compiler。這將創建一個新的Android項目的一些痛苦自動化,只是將一些* .9.png文件編譯爲Android本機格式。如果你要手動完成這個工作,你必須創建一個項目並放入一些* .9.png文件(「源文件」),將所有內容編譯成.apk格式,解壓縮.apk文件,然後找到*。 9.png文件,這是你發送給你的客戶的文件。

另外:我不知道,如果BitmapFactory.decodeStream知道這些PNG文件的NPTC塊,所以它可能會或可能無法正確處理圖像流。 Bitmap.getNinePatchChunk的存在表明BitmapFactory可能 - 您可以在上游代碼庫中查找它。

如果它不知道npTc塊和你的圖像顯着搞砸了,那麼我的答案會有所改變。

您不需要將編譯過的九張圖片發送到客戶端,而是編寫一個快速的Android應用程序來加載編譯後的圖片並吐出byte[]塊。然後,將這個字節數組和一個常規圖像一起傳輸給客戶端 - 沒有透明邊框,而不是「源」ninepatch圖像,而不是「編譯的」ninepatch圖像。您可以直接使用該塊創建您的對象。

另一種替代方法是使用對象序列化將九個圖像(NinePatch)發送給客戶端,例如使用JSON或內置序列化程序。

編輯如果你真的,真的需要構建自己的數據塊的字節數組,我會通過看do_9patchisNinePatchChunk,在ResourceTypes.cpp Res_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

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

梅德斯基巴東西:http://code.google.com/p/android4me/source/browse/src/android/graphics/Bitmap.java

+0

感謝kanzure給出了非常令人印象深刻的答案!我不敢相信我沒有想到將編譯的圖像發送給客戶端。我認爲你自己沒有用'BitmapFactory.decodeStream()'試過解決方案,對吧!?這是我做的:用abrc「編譯」圖像並將編譯後的圖像發送到客戶端。我用'BitmapFactory.decodeStream()'來解碼已編譯的位圖,但是'bitmap.getNin​​ePatchChunk()'返回null。我在模擬器和幾個目標(各種API級別)上都嘗試過它。似乎'BitmapFactory'無法正確解碼ninepatch圖像... =( – jacob11

+1

嗯,我能夠在編譯的九個patch上成功地使用BitmapFactory.decodeStream(),並且成功地爲我返回了塊。那我不知道爲什麼我寫了一個關於'decodeStream'是否知道這個塊的答案 - 當然它是這樣做的,否則我自己的嘗試不會工作,但它確實讓我擔心它是不適合你的,你能確認你編譯的九個補丁是否真的編譯好了嗎?你可以使用'diff'來檢查前/後是否相同。 – kanzure

+0

你是對的!確實可以顯示「已編譯」通過使用BitmapFactory.decodeStream()解碼它們來實現9個補丁!我通過設置一個小樣本項目來工作,該項目只通過網絡檢索「編譯」的9補丁圖像並將其顯示在UI中。其他東西弄亂我的原始應用程序。我使用圖像(磁盤)cac他可能會成爲問題......我現在就來看看吧!只要我找到原因,我會讓你知道!謝謝,kanzure! – jacob11

23

如果您需要動態創建9Patches看看這個要點我製作:https://gist.github.com/4391807

你可以將它傳遞給任何位圖,然後給它類似於iOS的cap insets。

+0

這正是我想知道的事情如果有可能,那麼謝謝你分享你的代碼片段。 – sradforth

+0

這對我來說非常完美,因爲我試圖完全複製跨平臺位圖的iOS功能,非常感謝! – Andre

+0

當使用提供的代碼時,我在定義「左/右/ ..」應該是什麼時遇到問題。我嘗試了使用UIEdgeInsets的值,但它不起作用。所以作爲最後的手段,我試圖使用這些值:左,寬右,頂部,高度 - 底部。不幸的是,雖然它不起作用 - 只有它的左上角和上半部分起作用。確實,這是結構,任何想法如何正確使用它?位圖應該是特殊形式嗎? – Panayotis

6

無需使用Android二進制資源編譯器準備編譯9patch png格式,只需使用AAPT在Android的SDK是確定的,在命令行是這樣的:
aapt.exe c -v -S /path/to/project -C /path/to/destination

+0

這應該是真正的接受答案。當APK構建時,9個補丁可繪製對象通過aapt(通常使用Eclipse)添加9個補丁塊。 draw9patch生成的9個修補程序PNG不包含其中的塊,並且確實將黑色線條添加到PNG中。 –

5

我創建了一個工具,從創建NinePatchDrawable (未編譯)NinePatch位圖。 見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是資產目錄下。

+0

感謝您分享這個有抱負的攻擊。但是,這種方法仍然會通過使用xml將NinePatch位圖背景與設置的背景進行比較。 – zionpi

+0

您的NinePatchBitmapFactory正是我需要我的問題:http://stackoverflow.com/questions/35203989/android-picasso-nine-patch-image-from-url。似乎工作正常。它可以免費使用嗎? – user2331454

+0

@zionpi是的,真正的NinePatch位圖更復雜。 – KNaito

2

工作和測試 - 運行時NINEPATCH CREATION

這是我實現的Android Ninepatch生成器,您可以通過提供任何位圖

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

創建通過這個類和代碼示例下方運行NinePatches

現在我們可以使用ninepatch生成器創建ninePatch或NinePatchDrawable或創建ninePatch塊。

例子:

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

只要複製粘貼到一個java文件NinePatchBuilder類代碼,並使用實例您的應用程序在運行時動態創建NinePatch,的任何決議。