it-swarm.com.ru

Создать NinePatch/NinePatchDrawable во время выполнения

У меня есть требование к моему приложению Android, чтобы части графики были настраиваемыми, получая новые цвета и изображения со стороны сервера. Некоторые из этих изображений являются изображениями из девяти патчей.

Я не могу найти способ создать и отобразить эти изображения из девяти патчей (которые были получены по сети).

Изображения из девяти патчей извлекаются и хранятся в приложении как растровые изображения. Чтобы создать NinePatchDrawable , вам понадобится соответствующий NinePatch или фрагмент (byte[]) NinePatch. NinePatch нельзя загружать из ресурсов, поскольку изображения не существуют в /res/drawable/. Кроме того, для создания NinePatch вам понадобится кусок NinePatch. Итак, все это сводится к кусочку.
Тогда возникает вопрос: как отформатировать/сгенерировать чанк из существующего растрового изображения (содержащего информацию NinePatch)?

Я искал исходный код Android и в Интернете, и я не могу найти какие-либо примеры этого. Что еще хуже, кажется, что все декодирование ресурсов NinePatch выполняется изначально.

У кого-нибудь был опыт с такого рода проблемами?

Я нацеливаюсь на уровень API 4, если это важно.

37
jacob11

getNinePatchChunk работает просто отлично. Он вернул null, потому что вы давали Bitmap «исходный» ninepatch. Нужно «скомпилированное» изображение из девяти патчей.

В мире Android существует два типа форматов файлов с девятью патчами («исходный» и «скомпилированный»). В исходной версии вы добавляете границу прозрачности 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 о фрагменте npTc в этих файлах png, поэтому он может правильно обрабатывать поток изображений. Существование Bitmap.getNinePatchChunk предполагает, что BitmapFactory может - вы можете посмотреть его в исходной кодовой базе.

Если он не знает о блоке npTc и ваши изображения сильно испорчены, тогда мой ответ немного изменится.

Вместо того чтобы отправлять скомпилированные изображения из девяти патчей клиенту, вы пишете быстрое приложение для Android, которое загружает скомпилированные изображения и выплевывает кусок byte[]. Затем вы передаете этот байтовый массив своим клиентам вместе с обычным изображением - без прозрачных границ, без «исходного» изображения NinePatch, а не скомпилированного Ninpatch-изображения. Вы можете напрямую использовать чанк для создания своего объекта.

Другой альтернативой является использование сериализации объектов для отправки изображений из девяти патчей (NinePatch) вашим клиентам, например, с помощью JSON или встроенного сериализатора.

Edit Если вам действительно нужно создать свой собственный массив байтов, я бы начал с просмотра do_9patch, isNinePatchChunk, Res_png_9patch и Res_png_9patch::serialize() в ResourceTypes.cpp. Также есть самодельный 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

Дмитрий Скиба: http://code.google.com/p/Android4me/source/browse/src/Android/graphics/Bitmap.Java

55
kanzure

Если вам нужно создать 9 патчей на лету, посмотрите этот Gist, который я сделал: https://Gist.github.com/4391807

Вы передаете ему любое растровое изображение, а затем даете ему вставки, похожие на iOS.

23
Brian Griffey

Нет необходимости использовать Android Binary Resource Compiler для подготовки скомпилированных png-файлов 9patch, просто с помощью aapt в Android-sdk все в порядке, командная строка выглядит следующим образом:
aapt.exe c -v -S /path/to/project -C /path/to/destination

6
figofuture

Я создаю инструмент для создания 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 находится в каталоге assets.

5
KNaito

Итак, вы в основном хотите создать NinePatchDrawable по запросу, не так ли? Я попробовал следующий код, возможно, он работает для вас:

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

Я думаю, что это должно работать. Вам просто нужно изменить первую строку, чтобы получить InputStream из Интернета. getNinePatchChunk() не предназначена для вызова от разработчиков в соответствии с документацией и может сломаться в будущем.

4
mreichelt

РАБОТАЕТ И ИСПЫТЫВАЕТСЯ - БЫСТРОЕ СОЗДАНИЕ NINEPATCH

Это моя реализация 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