web-dev-qa-db-ja.com

実行時にNinePatch / NinePatchDrawableを作成する

Androidアプリケーションで、サーバー側から新しい色と画像を取得することにより、グラフィックのパーツをカスタマイズできるようにする必要があります。これらの画像の一部は9パッチ画像です。

これらの9パッチ画像(ネットワーク経由で取得されたもの)を作成して表示する方法が見つかりません。

9パッチ画像が取得され、ビットマップとしてアプリケーションに保持されます。 NinePatchDrawable を作成するには、対応する NinePatch またはNinePatchのチャンク(byte[])が必要です。画像が/res/drawable/に存在しないため、NinePatchをリソースからロードできません。さらに、NinePatchを作成するには、NinePatchのチャンクが必要です。したがって、すべてチャンクまでドリルダウンします。
問題は、既存のビットマップ(NinePatch情報を含む)からチャンクをどのようにフォーマット/生成するかです。

AndroidソースコードとWebを検索しましたが、これの例を見つけることができません。さらに悪いことに、NinePatchリソースのすべてのデコードはネイティブで行われているようです。

誰かがこの種の問題について何か経験をしたことがありますか?

重要な場合は、APIレベル4を対象としています。

38
jacob11

getNinePatchChunkは問題なく動作します。 Bitmapに「ソース」ninepatchを与えていたため、nullを返しました。 「コンパイルされた」ninepatchイメージが必要です。

Android= world( "source" and "compiled")には、2つのタイプの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 Binary Resource Compiler を使用できます。これにより、新しいAndroidプロジェクトを作成して* .9.pngファイルをAndroidネイティブ形式にコンパイルするだけです。これを手動で行うには、基本的にプロジェクトを作成し、いくつかの* .9.pngファイル(「ソース」ファイル)を投入し、すべてを.apk形式にコンパイルし、.apkファイルを解凍してから、*。9.pngを見つけます。ファイル、それはあなたがあなたのクライアントに送るものです。

また、BitmapFactory.decodeStreamがこれらのpngファイルのnpTcチャンクを認識しているかどうかはわかりません。そのため、画像ストリームが正しく処理されている場合とそうでない場合があります。 Bitmap.getNinePatchChunkの存在は、BitmapFactoryがそうである可能性があることを示唆しています-上流のコードベースでそれを調べることができます。

NpTcチャンクがわからず、画像が大幅にねじ込まれている場合、私の答えは少し変わります。

コンパイルされたninepatchイメージをクライアントに送信する代わりに、簡単なAndroidアプリをコンパイルしてイメージをロードし、byte[]チャンクを吐き出します。次に、このバイト配列をクライアントに送信します。通常の画像-透明な境界線なし、「ソース」ninepatchイメージ、「コンパイル済み」ninepatchイメージではありません。チャンクを直接使用してオブジェクトを作成できます。

もう1つの方法は、オブジェクトのシリアル化を使用して、JSONや組み込みのシリアライザーなどを使用して、ninepatchイメージ(NinePatch)をクライアントに送信することです。

編集本当に、独自のチャンクバイト配列を構築する必要がある場合は、do_9patchisNinePatchChunkRes_png_9patchとResourceTypes.cppのRes_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

Dmitry Skibaスタッフ: http://code.google.com/p/Android4me/source/browse/src/Android/graphics/Bitmap.Java

55
kanzure

その場で9Patchesを作成する必要がある場合は、私が作成した次のGistを確認してください。 https://Gist.github.com/4391807

任意のビットマップを渡してから、iOSと同様のキャップインセットを与えます。

23
Brian Griffey

Android Binary Resource Compilerを使用してコンパイル済み9patch pngを準備する必要はありません。Android-sdkでaaptを使用するだけで十分です。コマンドラインは次のようになります。
aapt.exe c -v -S /path/to/project -C /path/to/destination

6
figofuture

(コンパイルされていない)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ディレクトリの下にあります。

6
KNaito

ですから、基本的にNinePatchDrawableをオンデマンドで作成したいのですね。私は次のコードを試しました、おそらくそれはあなたのために機能します:

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

これでうまくいくと思います。 WebからInputStreamを取得するには、最初の行を変更するだけです。 getNinePatchChunk()は、ドキュメントに従って開発者から呼び出されることを意図しておらず、将来は機能しなくなる可能性があります。

4
mreichelt

動作とテスト-ランタイムナインパッチの作成

これは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ビルダーを使用して、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を作成します。

2
Diljeet

Bitmapクラスは、これを行うためのメソッドを提供しますyourbitmap.getNinePatchChunk()。私はそれを使用したことがありませんが、それはあなたが探しているもののようです。

0
FoamyGuy