web-dev-qa-db-fra.com

Créer un NinePatch/NinePatchDrawable en exécution

Sur mon application Android, je souhaite que certaines parties des graphiques soient personnalisables, en récupérant de nouvelles couleurs et de nouvelles images côté serveur. Certaines de ces images sont des images de neuf patchs.

Je ne trouve pas de moyen de créer et d’afficher ces images de neuf correctifs (qui ont été récupérées sur le réseau).

Les images de neuf patchs sont récupérées et conservées dans l'application sous forme de bitmaps. Pour créer un NinePatchDrawable , vous avez besoin du NinePatch ou du chunk (byte[]) correspondant du NinePatch. Le NinePatch NE PEUT PAS être chargé à partir des ressources, car les images n'existent pas dans /res/drawable/. De plus, pour créer le NinePatch, vous avez besoin du morceau du NinePatch. Donc, tout se passe au morceau.
La question est alors: comment formater/générer le bloc à partir d’un bitmap existant (contenant les informations de NinePatch)?

J'ai effectué des recherches dans le code source Android et sur le Web et je n'arrive pas à trouver d'exemples. Pour aggraver les choses, tout le décodage d’une ressource NinePatch semble se faire de manière native.

Quelqu'un at-il eu des expériences avec ce genre de problème?

Je cible l'API de niveau 4, si cela est important.

37
jacob11

getNinePatchChunk fonctionne très bien. Elle a renvoyé la valeur null car vous donniez à Bitmap un neufpatch "source". Il faut une image de neuf images "compilées".

Il existe deux types de formats de fichier ninepatch dans le monde Android ("source" et "compilé"). La version source est l'endroit où vous ajoutez la bordure de transparence 1px partout: lorsque vous compilerez votre application dans un fichier .apk plus tard, aapt convertira vos fichiers * .9.png au format binaire attendu par Android. C'est ici que le fichier png obtient ses métadonnées "chunk". ( Lire la suite )

Bon, je suis maintenant aux affaires (vous écoutez DJ kanzure).

  1. Code client, quelque chose comme ceci:

    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. Côté serveur, vous devez préparer vos images. Vous pouvez utiliser le compilateur de ressources binaires Android . Cela automatise une partie de la peine de créer un nouveau projet Android simplement pour compiler des fichiers * .9.png au format natif Android. Si vous deviez le faire manuellement, vous feriez essentiellement un projet et incorporeriez des fichiers * .9.png (fichiers "sources"), tout compiler au format .apk, décompressez le fichier .apk, puis recherchez le fichier *. 9.png, et c’est celui que vous envoyez à vos clients.

Aussi: je ne sais pas si BitmapFactory.decodeStream connaît l'existence du bloc npTc dans ces fichiers png, il est donc possible que le flux d'images soit traité correctement. L'existence de Bitmap.getNinePatchChunk suggère que BitmapFactory pourrait-- vous pourriez aller le chercher dans la base de code en amont.

Dans le cas où il ne connaît pas le bloc npTc et que vos images sont gâchées de manière significative, ma réponse change un peu.

Au lieu d'envoyer les images de neuf correctifs compilés au client, vous écrivez une application Android rapide pour charger les images compilées et cracher le fragment byte[]. Ensuite, vous transmettez ce tableau d'octets à vos clients avec une image normale - pas de bordures transparentes, pas l'image à neuf sources "source", pas l'image à neuf "séries" compilée. Vous pouvez directement utiliser le morceau pour créer votre objet.

Une autre solution consiste à utiliser la sérialisation d'objet pour envoyer des images à neuf correctifs (NinePatch) à vos clients, par exemple avec JSON ou le sérialiseur intégré.

Edit Si vous avez vraiment, vraiment besoin de construire votre propre tableau d'octets de morceaux, je commencerais par regarder do_9patch, isNinePatchChunk, Res_png_9patch et Res_png_9patch::serialize() dans ResourceTypes.cpp. Il y a aussi un lecteur de morceaux npTc fait maison de Dmitry Skiba. Je ne peux pas poster de liens, donc si quelqu'un peut modifier ma réponse, ce serait 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: http://code.google.com/p/Android4me/source/browse/src/Android/graphics/Bitmap.Java

55
kanzure

Si vous avez besoin de créer 9Patches à la volée, consultez cette Gist que j’ai faite: https://Gist.github.com/4391807

Vous transmettez une image bitmap, puis lui donnez des encoches similaires à iOS.

23
Brian Griffey

Pas besoin d'utiliser Android Binary Resource Compiler pour préparer les pngs 9patch compilés, il est correct d'utiliser aapt dans Android-sdk.
aapt.exe c -v -S /path/to/project -C /path/to/destination

6
figofuture

Je crée un outil pour créer NinePatchDrawable à partir du bitmap (non compilé) NinePatch . Voir https://Gist.github.com/knight9999/86bec38071a9e0a781ee .

La méthode NinePatchDrawable createNinePatchDrawable(Resources res, Bitmap bitmap) Vous aide.

Par exemple,

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

où 

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

Dans cet exemple de cas, le my_nine_patch_image.9.png se trouve sous le répertoire des actifs.

5
KNaito

Donc, vous voulez créer une NinePatchDrawable à la demande, n'est-ce pas? J'ai essayé le code suivant, peut-être que cela fonctionne pour vous:

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

Je pense que cela devrait fonctionner. Il vous suffit de modifier la première ligne pour obtenir InputStream à partir du Web. getNinePatchChunk() n'est pas destiné à être appelé par les développeurs conformément à la documentation et pourrait ne plus fonctionner correctement.

4
mreichelt

TRAVAIL ET TESTÉ - CRÉATION NINEPATCH À DURÉE

C’est mon implémentation d’Android Ninepatch Builder, vous pouvez créer NinePatches à l’exécution à travers cette classe et des exemples de code ci-dessous en fournissant des 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.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;
    }
}

Nous pouvons maintenant utiliser le générateur ninepatch pour créer NinePatch ou NinePatchDrawable ou pour créer un fragment NinePatch.

Exemple:

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

Il vous suffit de copier-coller le code de classe NinePatchBuilder dans un fichier Java et d’utiliser les exemples pour créer NinePatch à la volée lors de l’exécution de votre application, quelle que soit la résolution.

2
Diljeet

La classe Bitmap fournit une méthode pour cela yourbitmap.getNinePatchChunk(). Je ne l'ai jamais utilisé, mais il semble que ce que vous cherchiez.

0
FoamyGuy