android - Crea un NinePatch/NinePatchDrawable en tiempo de ejecución
networking runtime (7)
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 mapa de bits 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 decodificación 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.
Creo una herramienta para crear NinePatchDrawable desde (sin compilar) mapa de bits 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.
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.
No es necesario usar el Compilador de recursos binarios de Android para preparar compilan 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
Si necesitas crear 9Patches sobre la marcha, echa un vistazo a esta idea que hice: https://gist.github.com/4391807
Usted pasa cualquier mapa de bits y luego le da insertos de tapa similares a iOS.
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<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;
}
}
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.
getNinePatchChunk
funciona bien. Se devolvió nulo porque le estabas dando a Bitmap
un parche "fuente" nueve. Necesita una imagen de nueve parches "compilada".
Hay dos tipos de formatos de archivo de nueve parches en el mundo de Android ("fuente" y "compilado"). La versión de origen es donde agrega el borde de transparencia de 1 px en todas partes: cuando compila 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).
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);
En el lado del servidor, debes preparar tus imágenes. Puede usar el compilador de recursos binarios de Android . Esto automatiza parte del dolor de crear un nuevo proyecto de Android solo para compilar 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"), compilarí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 compiladas al cliente, usted escribe una aplicación rápida de Android para cargar imágenes compiladas 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 "compilado". 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