Crea un NinePatch / NinePatchDrawable en tiempo de ejecución

Tengo un requisito en mi aplicación de Android que las partes de los gráficos deben ser personalizables, recuperando nuevos colores e imágenes desde el lado del servidor. Algunas de estas imágenes son imágenes de nueve parches.

No puedo encontrar una manera de crear y mostrar estas imágenes de nueve parches (que se han recuperado a través de la red).

Las imágenes de nueve parches se recuperan y guardan en la aplicación como mapas de bits. Para crear un NinePatchDrawable , necesitas el NinePatch correspondiente o el fragmento ( byte[] ) del NinePatch. El NinePatch NO se puede cargar desde los Recursos, ya que las imágenes no existen en /res/drawable/ . Además, para crear el NinePatch, necesita la porción del NinePatch. Entonces, todo se reduce al trozo.
La pregunta es, entonces, ¿cómo se formatea / genera un fragmento de un bitmap existente (que contiene la información de NinePatch)?

He buscado a través del código fuente de Android y la Web y parece que no puedo encontrar ningún ejemplo de esto. Para empeorar las cosas, toda la deencoding de los recursos de un NinePatch parece realizarse de forma nativa.

¿Alguien ha tenido alguna experiencia con este tipo de problema?

Apuntaré al nivel 4 de API, si eso es importante.

getNinePatchChunk funciona bien. Se devolvió nulo porque le estabas dando a Bitmap un parche “fuente” nueve. Necesita una imagen de nueve parches “comstackda”.

Hay dos tipos de formatos de archivo de nueve parches en el mundo de Android (“fuente” y “comstackdo”). La versión de origen es donde agrega el borde de transparencia de 1 px en todas partes: cuando comstack su aplicación en una .apk más tarde, aapt convertirá sus archivos * .9.png al formato binario que Android espera. Aquí es donde el archivo png obtiene sus metadatos “fragmento”. ( leer más )

De acuerdo, ahora a por negocios (estás escuchando DJ kanzure).

  1. Código de cliente, algo como esto:

     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. En el lado del servidor, debes preparar tus imágenes. Puede usar el comstackdor de recursos binarios de Android . Esto automatiza parte del dolor de crear un nuevo proyecto de Android solo para comstackr algunos archivos * .9.png en el formato nativo de Android. Si hicieras esto manualmente, básicamente harías un proyecto y lanzarías algunos archivos * .9.png (archivos “fuente”), comstackrías todo en el formato .apk, descomprimir el archivo .apk, y luego buscar el *. 9.png archivo, y ese es el que envía a sus clientes.

Además: no sé si BitmapFactory.decodeStream conoce el fragmento npTc en estos archivos png, por lo que puede tratar o no el flujo de imágenes correctamente. La existencia de Bitmap.getNinePatchChunk sugiere que BitmapFactory podría– podrías ir a buscarlo en la base de código ascendente.

En el caso de que no sepa sobre el fragmento npTc y sus imágenes estén siendo manipuladas significativamente, entonces mi respuesta cambia un poco.

En lugar de enviar las nueve imágenes comstackdas al cliente, usted escribe una aplicación rápida de Android para cargar imágenes comstackdas y escupir el fragmento byte[] . Luego, usted transmite esta matriz de bytes a sus clientes junto con una imagen regular, sin bordes transparentes, ni la imagen del parche del “origen”, ni la imagen del parche del nueve “comstackdo”. Puedes usar directamente el fragmento para crear tu objeto.

Otra alternativa es usar la serialización de objetos para enviar nueve imágenes de parches ( NinePatch ) a sus clientes, como con JSON o el serializador incorporado.

Editar Si realmente, realmente necesitas construir tu propia matriz de bytes de fragmentos, empezaría por mirar do_9patch , isNinePatchChunk , Res_png_9patch y Res_png_9patch::serialize() en ResourceTypes.cpp. También hay un lector de fragmentos npTc de Dmitry Skiba hecho en casa. No puedo publicar enlaces, por lo que si alguien puede editar mi respuesta sería genial.

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

Material de Dmitry Skiba: http://code.google.com/p/android4me/source/browse/src/android/graphics/Bitmap.java

Si necesitas crear 9Patches sobre la marcha, echa un vistazo a esta idea que hice: https://gist.github.com/4391807

Usted pasa cualquier bitmap y luego le da insertos de tapa similares a iOS.

No es necesario usar el Comstackdor de recursos binarios de Android para preparar comstackn 9ngch pngs, solo usa aapt en android-sdk está bien, la línea de comandos es así:
aapt.exe c -v -S /path/to/project -C /path/to/destination

Creo una herramienta para crear NinePatchDrawable desde (sin comstackr) bitmap NinePatch. Ver https://gist.github.com/knight9999/86bec38071a9e0a781ee .

El método NinePatchDrawable createNinePatchDrawable(Resources res, Bitmap bitmap) te ayuda.

Por ejemplo,

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

dónde

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

En este caso de ejemplo, my_nine_patch_image.9.png encuentra en el directorio de activos.

Entonces, básicamente, quieres crear un NinePatchDrawable a pedido, ¿no? Probé el siguiente código, tal vez te funcione:

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

Creo que esto debería funcionar. Solo tienes que cambiar la primera línea para obtener el InputStream de la web. getNinePatchChunk() no debe ser llamado por los desarrolladores de acuerdo con la documentación, y podría romperse en el futuro.

TRABAJANDO Y PROBADO – CREACIÓN DE NINEPATCH RUNTIME

Esta es mi implementación de Android Ninepatch Builder, puede crear NinePatches en Runtime a través de esta clase y ejemplos de código a continuación, suministrando cualquier Bitmap

 public class NinePatchBuilder { int width,height; Bitmap bitmap; Resources resources; private ArrayList xRegions=new ArrayList(); private ArrayList yRegions=new ArrayList(); 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 

Ahora podemos usar el generador de nueve parches para crear NinePatch o NinePatchDrawable o para crear NinePatch Chunk.

Ejemplo:

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

Simplemente copie y pegue el código de la clase NinePatchBuilder en un archivo java y use los ejemplos para crear NinePatch sobre la marcha durante el tiempo de ejecución de su aplicación, con cualquier resolución.

La clase Bitmap proporciona un método para hacer esto: yourbitmap.getNinePatchChunk() . Nunca lo he usado, pero parece que eso es lo que estás buscando.