Создайте NinePatch/NinePatchDrawable во время выполнения
У меня есть требование в приложении для Android, что части на графике должны быть настраиваемы, путем получения новых цветов и изображений со стороны сервера. Некоторые из этих изображений являются изображениями с девятью патчами.
Я не могу найти способ создания и отображения этих изображений с девятью патчами (которые были получены по сети).
Изображения с девятью патчами извлекаются и сохраняются в приложении в виде растровых изображений. Чтобы создать NinePatchDrawable, вам понадобится соответствующий NinePatch или фрагмент (byte[]
) для NinePatch. NinePatch НЕ загружается из ресурсов, поскольку изображения не существуют в /res/drawable/
. Кроме того, для создания NinePatch вам нужен фрагмент NinePatch. Итак, все это сводится к куску.
Вопрос в том, как один форматировать/генерировать фрагмент из существующего растрового изображения (содержащий информацию NinePatch)?
Я искал в исходном коде Android и в Интернете, и я не могу найти никаких примеров этого. Чтобы все ухудшилось, все декодирование ресурсов NinePatch, по-видимому, выполняется изначально.
Есть ли у кого-нибудь опыт такого рода проблем?
Я нацелен на уровень API 4, если это важно.
Ответы
Ответ 1
getNinePatchChunk
работает отлично. Он возвратил null, потому что вы давали Bitmap
"исходный" девятипакет. Он нуждается в "скомпилированном" девятикратном изображении.
В мире Android существует два типа форматов файлов с девятью файлами ( "источник" и "скомпилированный" ). Исходная версия - это то, где вы добавляете границу прозрачности 1px везде - когда вы скомпилируете свое приложение в .apk позже, aapt преобразует ваши файлы *.9.png в бинарный формат, который ожидает Android. Здесь png файл получает свои метаданные "chunk". (читать дальше)
Хорошо, теперь дело до бизнеса (вы слушаете DJ kanzure).
-
Код клиента, что-то вроде этого:
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);
-
На стороне сервера вам нужно подготовить свои изображения. Вы можете использовать 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
) вашим клиентам, например, с помощью JSON или встроенного сериализатора.
Изменить Если вам действительно нужно создать собственный массив байтовых блоков, я бы начал с поиска do_9patch
, isNinePatchChunk
, Res_png_9patch
и Res_png_9patch::serialize()
в ResourceTypes.cpp, Там также есть самодельный читатель npTc chunk от Дмитрия Скиба. Я не могу размещать ссылки, поэтому, если кто-то может отредактировать мой ответ, это будет круто.
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
Ответ 2
Если вам нужно создать 9Patches на лету, проверьте этот gist, который я сделал: https://gist.github.com/4391807
Вы передаете ему любое растровое изображение, а затем даете ему вставки с крышкой, похожие на iOS.
Ответ 3
Не нужно использовать компилятор двоичных ресурсов Android для подготовки скомпилированных 9patch-png, просто использование aapt в android-sdk в порядке, командная строка выглядит так:
aapt.exe c -v -S /path/to/project -C /path/to/destination
Ответ 4
Я создаю инструмент для создания 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
находится под каталогом ресурсов.
Ответ 5
Итак, вы в основном хотите создать NinePatchDrawable
по запросу, не так ли? Я попробовал следующий код, возможно, он работает для вас:
InputStream in = getResources().openRawResource(R.raw.test);
Drawable d = NinePatchDrawable.createFromStream(in, null);
System.out.println(d.getMinimumHeight() + ":" + d.getMinimumHeight());
Я думаю, это должно сработать. Вам просто нужно изменить первую строку, чтобы получить InputStream из Интернета. getNinePatchChunk()
не должен вызываться разработчиками в соответствии с документацией и может разорваться в будущем.
Ответ 6
РАБОТА И ИСПЫТАНИЕ - СОГЛАШЕНИЕ НИНЕПТИЧЕСКОГО ПРОТИВНИКА
Это моя реализация Android Ninepatch Builder, вы можете создавать NinePatches в Runtime через этот класс и примеры кода ниже, поставляя любой 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;
}
}
Теперь мы можем использовать конструктор 9patch для создания 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 на лету во время работы приложения с любым разрешением.
Ответ 7
Класс Bitmap предоставляет метод для этого yourbitmap.getNinePatchChunk()
. Я никогда не использовал его, но, похоже, это то, что вы ищете.