it-swarm.com.de

Erstellen Sie ein NinePatch/NinePatchDrawable zur Laufzeit

Ich habe in meiner Android-Anwendung die Anforderung, dass Teile der Grafiken anpassbar sein sollten, indem neue Farben und Bilder von der Serverseite abgerufen werden. Einige dieser Bilder sind Bilder mit neun Patches.

Ich kann keine Möglichkeit finden, diese neun Patches (die über das Netzwerk abgerufen wurden) zu erstellen und anzuzeigen.

Die neun Patch-Bilder werden abgerufen und als Bitmaps in der Anwendung gehalten. Um ein NinePatchDrawable zu erstellen, benötigen Sie entweder das entsprechende NinePatch oder den Chunk (byte[]) des NinePatch. Das NinePatch kann NICHT aus den Ressourcen geladen werden, da die Bilder in /res/drawable/ nicht vorhanden sind. Um den NinePatch zu erstellen, benötigen Sie außerdem den Teil des NinePatch. Also alles drillt bis zum Stück.
.__ Die Frage ist dann, wie man einen Chunk aus einer vorhandenen Bitmap (die die NinePatch-Informationen enthält) formatiert/generiert.

Ich habe den Android-Quellcode und das Web durchsucht, und ich kann keine Beispiele dafür finden. Zu allem Überfluss scheint die Dekodierung einer NinePatch-Ressource nativ zu sein.

Hat jemand Erfahrungen mit dieser Art von Problem gemacht?

Ich ziele auf API-Level 4 ab, wenn dies von Bedeutung ist.

37
jacob11

getNinePatchChunk funktioniert gut. Es wurde null zurückgegeben, weil Sie Bitmap ein "source" -Nunpatch gaben. Es braucht ein "kompiliertes" 9patch-Image.

Es gibt zwei Arten von Ninepatch-Dateiformaten in der Android-Welt ("source" und "compiled"). In der Quellversion fügen Sie überall den 1px-Transparenzrahmen hinzu. Wenn Sie Ihre App später in eine .apk-Datei kompilieren, konvertiert aapt Ihre * .9.png-Dateien in das Binärformat, das Android erwartet. Hier erhält die PNG-Datei ihre "Chunk" -Metadaten. ( Weiterlesen )

Okay, jetzt zur Sache (Sie hören DJ Kanzure).

  1. Client-Code, etwa so:

    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. Serverseitig müssen Sie Ihre Bilder vorbereiten. Sie können den Android Binary Resource Compiler verwenden. Dies automatisiert einige der Schwierigkeiten, ein neues Android-Projekt zu erstellen, nur um einige * .9.png-Dateien in das native Android-Format zu kompilieren. Wenn Sie dies manuell tun würden, würden Sie im Wesentlichen ein Projekt erstellen und einige * .9.png-Dateien ("Quelldateien") einfügen, alles im .apk-Format kompilieren, die .apk-Datei entpacken und das * suchen. 9.png-Datei, und das ist die Datei, die Sie an Ihre Kunden senden.

Außerdem: Ich weiß nicht, ob BitmapFactory.decodeStream über den npTc-Block in diesen PNG-Dateien Bescheid weiß, so dass der Bilddatenstrom möglicherweise nicht richtig behandelt wird. Die Existenz von Bitmap.getNinePatchChunk legt nahe, dass BitmapFactory könnte-- Sie könnten in der Upstream-Codebase nachschlagen.

Für den Fall, dass es nicht über den npTc-Block weiß und Ihre Bilder erheblich vermasselt werden, ändert sich meine Antwort ein wenig.

Anstatt die kompilierten 9-Patch-Bilder an den Client zu senden, schreiben Sie eine schnelle Android-App, um kompilierte Bilder zu laden und den byte[]-Block auszuspucken. Anschließend übertragen Sie dieses Byte-Array zusammen mit einem regulären Bild an Ihre Clients - keine transparenten Rahmen, nicht das "Quell" -Nunpatch-Bild, nicht das "kompilierte" Neunpatch-Bild. Sie können den Block direkt verwenden, um Ihr Objekt zu erstellen.

Eine andere Alternative besteht darin, Objektserialisierung zum Senden von Neunpatch-Bildern (NinePatch) an Ihre Clients zu verwenden, z. B. mit JSON oder dem integrierten Serializer.

Edit Wenn Sie wirklich Ihr eigenes Chunk-Byte-Array erstellen müssen, würde ich zunächst do_9patch, isNinePatchChunk, Res_png_9patch und Res_png_9patch::serialize() in ResourceTypes.cpp betrachten. Es gibt auch einen hausgemachten npTc-Chunk-Reader von Dmitry Skiba. Ich kann keine Links posten. Wenn also jemand meine Antwort bearbeiten kann, wäre das 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-Zeug: http://code.google.com/p/Android4me/source/browse/src/Android/graphics/Bitmap.Java

55
kanzure

Wenn Sie 9Patches on the fly erstellen müssen, schauen Sie sich diese Gist an, die ich gemacht habe: https://Gist.github.com/4391807

Sie übergeben es ein beliebiges Bitmap und geben dann wie bei iOS Cap-Insets ein.

23
Brian Griffey

Sie müssen keine Android Binary Resource Compiler verwenden, um kompilierte 9patch-Dateien vorzubereiten. Die Verwendung von aapt in Android-sdk ist in Ordnung.
aapt.exe c -v -S /path/to/project -C /path/to/destination

6
figofuture

Ich erstelle ein Tool zum Erstellen von NinePatchDrawable aus der (nicht kompilierten) NinePatch-Bitmap ..__ Siehe https://Gist.github.com/knight9999/86bec38071a9e0a781ee .

Die Methode NinePatchDrawable createNinePatchDrawable(Resources res, Bitmap bitmap) Hilft Ihnen dabei.

Zum Beispiel,

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

woher 

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

In diesem Beispielfall befindet sich my_nine_patch_image.9.png im Assets-Verzeichnis.

5
KNaito

Sie möchten also grundsätzlich eine NinePatchDrawable on demand erstellen, oder? Ich habe folgenden Code ausprobiert, vielleicht funktioniert er für Sie:

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

Ich denke das sollte funktionieren. Sie müssen nur die erste Zeile ändern, um den InputStream aus dem Web zu erhalten. getNinePatchChunk() ist laut der Dokumentation nicht dafür gedacht, von Entwicklern aufgerufen zu werden, und könnte in Zukunft brechen.

4
mreichelt

ARBEITEN UND GETESTET - RUNTIME NINEPATCH CREATION

Dies ist meine Implementierung von Android Ninepatch Builder. Sie können NinePatches zur Laufzeit mithilfe dieser Klasse und den folgenden Codebeispielen erstellen, indem Sie eine beliebige Bitmap bereitstellen

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

Nun können wir den ninepatch Builder verwenden, um NinePatch oder NinePatchDrawable oder NinePatch Chunk zu erstellen.

Beispiel:

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

Kopieren Sie einfach den Code der NinePatchBuilder-Klasse in eine Java-Datei. Verwenden Sie die Beispiele, um NinePatch während der Laufzeit Ihrer App in beliebiger Auflösung zu erstellen.

2
Diljeet

Die Bitmap-Klasse stellt hierfür eine Methode zur Verfügung yourbitmap.getNinePatchChunk(). Ich habe es noch nie benutzt, aber es scheint, dass Sie danach suchen.

0
FoamyGuy