Создайте NinePatch / NinePatchDrawable во время выполнения - PullRequest
37 голосов
/ 22 февраля 2011

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

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

Девятьизображения-патчи извлекаются и хранятся в приложении как растровые изображения.Чтобы создать NinePatchDrawable , вам потребуется либо NinePatch , либо блок (byte[]) NinePatch.NinePatch НЕ МОЖЕТ быть загружено из Ресурсов, так как изображения не существуют в /res/drawable/.Кроме того, для создания NinePatch вам понадобится кусок NinePatch.Итак, все сводится к фрагменту.
Вопрос в том, как отформатировать / сгенерировать фрагмент из существующего растрового изображения (содержащего информацию NinePatch)?

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

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

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

Ответы [ 7 ]

55 голосов
/ 02 апреля 2011

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

В мире Android существует два типа форматов файлов 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 .Это автоматизирует некоторые трудности, связанные с созданием нового проекта Android, просто для компиляции некоторых файлов * .9.png в собственный формат Android.Если бы вы делали это вручную, вы бы по сути сделали проект и добавили несколько файлов * .9.png («исходные» файлы), скомпилировали все в формат .apk, разархивировали файл .apk, а затем нашли *.Файл 9.png, и это тот, который вы отправляете своим клиентам.

Также: я не знаю, знает ли BitmapFactory.decodeStream о npTc чанке в этих файлах png, поэтомуможет или не может обрабатывать поток изображения правильно.Существование Bitmap.getNinePatchChunk предполагает, что BitmapFactory может - вы можете посмотреть его в исходной кодовой базе.

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

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

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

Редактировать Если вам действительно нужно создать свой собственный массив байтов, я бы начал с просмотра 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

23 голосов
/ 28 декабря 2012

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

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

6 голосов
/ 03 июня 2013

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

5 голосов
/ 23 апреля 2015

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

4 голосов
/ 22 февраля 2011

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

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

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

2 голосов
/ 13 июня 2016

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

Это моя реализация Ninepatch Builder для Android, вы можете создавать 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 на лету во время выполнения приложения с любым разрешением.

0 голосов
/ 22 февраля 2011

Класс Bitmap предоставляет метод для этого yourbitmap.getNinePatchChunk().Я никогда не использовал его, но, похоже, это то, что вы ищете.

...