miércoles, 20 de junio de 2012

lightbyte.org


Hola a todos.
Este es el último post que escribiré en este blog porque me he mudado a mi propio dominio http://www.lightbyte.org :)
En mi sitio web no sólo hablaré de Android, sino de desarrollo web, Ubuntu y, en general, de todo lo que quiera.
Espero verte por allí la próxima vez!


--------English version--------

Hi all.
This is the last post I will write in this blog because I've moved to my own domain http://www.lightbyte.org :p
In my own website I will not talk only about Android but web development, Ubuntu and, in general, about whatever I want.
I hope to see you there next time!


viernes, 29 de julio de 2011

Nuevo diseño de "Say It Quietly"

El primer post donde anuncié el proyecto "Say It Quietly" mostraba una interfaz gráfica poco trabajada. No es que ahora haya trabajado mucho más, pero es que yo no soy diseñador y para mi el mundo de las imágenes es complicado.
Bueno, se que no es nada del otro mundo y que queda mucho por hacer. Algo mejorada con respecto al anterior diseño sí que está.
Espero que siga siendo intuitiva!
Creo que sea como fuere, se quedará así algún tiempecito...


¿Se te ocurre algún consejo o mejora?

Comportamientos del Canvas.drawBitmap()

Intentando dar soporte a mi aplicación Say It Quietly para todas las versiones de Android he dado con un problema al que, al comienzo de este post, no tengo muy claro como arreglarlo. Al menos se me ocurre una solución posible.

El problema:

Antes de nada, voy a intentar describir el problema. El texto es escrito en un bitmap. En cada repetición del thread voy recorriendo el bitmap con un Rect de ancho fijo (el alto de la pantalla, o el ancho si hablamos de landscape). El problema viene cuando el bitmap entra en escena, el Rect tiene valores negativos; o cuando el bitmap sale de escena, el right del Rect tiene valores que no pertenecen al bitmap.


En este gráfico se ve que tanto al aparecer el bitmap en pantalla como al salir de ella el Rect abarca un espacio vacío. Esto causa que en versiones de Android anteriores a la 2.2 drawBitmap estire la imagen al pintarla en la pantalla. Esto es lo que se ve:

Pantallazos de la entrada del texto en escena. Se puede apreciar como se estira la "S":
  


Pantallazos de la salida del texto de la escena. Se puede apreciar como se estira la "Y":
  



Solución fácil:

Es fácil porque no hay que pensar nada, simplemente programar para ajustar los Rect pasados al drawBitmap, de esta forma no habrá stretching.

Solución difícil:

Buscar si existe algún parámetro en el Canvas o el Paint o en algún sitio para cambiar el comportamiento del drawBitmap dependiendo de la versión de Android en que estemos corriendo la aplicación para que no haga stretching. Esto es "dificil" porque no se si existe algo así.

Cualquier idea para solucionar esto será bien recibida. Mientras tanto yo seguiré por el camino "fácil" que es un lío con los Rect, las posiciones y demás.

jueves, 21 de julio de 2011

Mi primer proyecto, "Say It Quietly"

Por fin puedo enseñaros algo de mi aplicación Say It Quietly.
La empecé hace ya casi un año, septiembre de 2010. No es que lleve todo este tiempo trabajando en ella. La he dedicado más bien poco tiempo.

La idea:

La idea es poder escribir un texto con la letra más grande que quepa en la pantalla. Parece muy sencillo, no? Pues veréis como la puedo liar parda. Al menos tengo la excusa que estoy aprendiendo... jeje

El market:

Después de unas búsquedas por Internet y el Market no encontré más que una aplicación que hiciera lo que había pensado, y casi incluso coincidimos en el nombre, la otra se llama "Say It Laud". Como veis los conceptos son diferentes jeje.

Funcionamiento:

Al ejecutar la aplicación aparece la pantalla donde escribir el texto a mostrar, elegir la velocidad de scroll, el color de fondo y color de texto (en próximas versiones) y un botón para cambiar a la otra pantalla.
La otra pantalla será la de mostrar el texto a pantalla completa quitando la barra de estado y haciendo el texto lo más grande posible.

Interfaz de usuario actual (en desarrollo, son sólo pruebas de diseño):
Pantalla 1:

Pantalla 2:


Problemas encontrados:

  • Selector de color. Todavía estoy pensando como hacer un selector de color chulo que no sea una simple lista con colores predefinidos ya que no he encontrado ningún widget para esto.
  • Cálculo del tamaño del texto. Hay que sacar el tamaño de la pantalla y la densidad para saber qué tamaño de letra establecer. Pongo un gráfico explicativo de las dimensiones de un glifo (glyph) sacado de la wikipedia.



    public void buildBitmap(int width, int height) {

        // Establezco algunos parámetros del  Paint que 
        // usaré para pintar el texto.
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setAntiAlias(true);
        paint.setTypeface(Typeface.MONOSPACE);

        // Obtengo la densidad para calcular el tamaño del texto
        float density = getContext().getResources()
                                  .getDisplayMetrics().density;
        // Establezco el tamaño del texto
        paint.setTextSize(height * density);

        // Obtengo las FontMetrics para ver cuanto desciende 
        // la fuente desde la línea base y así poder 
        // centrar el texto verticalmente.
        FontMetricsInt fmi = new FontMetricsInt();
        paint.getFontMetricsInt(fmi);

        // Para sacar el ancho del texto uso el measureText.
        // La función paint.getTextBounds() me devolvía el 
        // ancho pero añadiendo por delante espacio y quitándolo 
        // de detrás, así que se quedaba el último carácter 
        // un poco cortado.
        int measureWidth = (int)paint.measureText(mText);

        // Creo el bitmap del ancho del texto y 
        // el alto de la pantalla
        mBitmap = Bitmap.createBitmap(measureWidth, height,
                                                             Bitmap.Config.RGB_565);

        // Creo un canvas que me permitirá editar el bitmap creado
        Canvas canvas = new Canvas(mBitmap);
        // Color de fondo y pinto el fondo
        paint.setColor(mBackColor);
        canvas.drawRect(0, 0, measureWidth, height, paint);


        // Color de texto y pinto el texto.
        // Los parámetros del drawText son el texto, 
        // la posición X e Y y el objeto Paint.
        // La Y tiene que ser donde quieres que esté la 
        // línea base de tu texto. Y hay que contar con lo que 
        // desciende, es decir que si quieres que se vean los 
        // palitos de las "p", las "q" etc, tienes que hacer
        // esta operación.
        paint.setColor(mForeColor);
        canvas.drawText(mText, 0,
                                    (float)mBitmap.getHeight() - fmi.descent,
                                    paint);

    }

    Bueno en el código ya he explicado todo, sólo añadir que si alguien ve algo raro como el cálculo del tamaño del texto (height * density) que me lo diga porque no tengo muy claro que sea así, sólo he probado en 1 emulador y en mi HTC Desire y va bien!.
  • Procedimiento de mover el texto:
    • Un simple loop. Bloquea la aplicación. La primera versión del programa no podía regresar a la pantalla anterior porque el loop bloqueaba todo.
    • Un thread. Buena opción, pero no va suave. Ya podía regresar de la pantalla del texto pero el texto en movimiento iba a tirones y con problemas de refrescos. Todo debido a que todavía escribía el texto en la pantalla en cada iteración del thread.
    • Escribir el texto cada iteración. Demasiados drawText y new Paint en la hebra de pintado. Con un drawText en cada iteración ya era suficiente para ralentizar. Mejor crear bitmap con el texto en el constructor y que la hebra maneje objetos creados anteriormente.
Aún así no estoy contento con el resultado, sigo buscando como hacer esto para que el texto se vea fluido y suave (Corrijo: Sí estoy contento! he probado el programa en el HTC y va genial! el emulador es una castaña comparado)
De todas formas el siguiente paso será trabajar con las animaciones (clase Animator) y probar OpenGL.

    martes, 19 de julio de 2011

    Optimización del For (for-each)

    Hace tiempo que conozco el for-each y nunca me he parado a pensar qué utilidad podría tener ese for teniendo uno que hace lo que quiero y no necesito ni pensar como usarlo.
    En las plataformas móviles parece que es aún más útil usar estas estructuras por lo de ahorrar tiempo de proceso y no gastar bateria.

    El for-each puede usarse para colecciones que implementan el interfaz Iterable y para Arrays. Con las colecciones, cada elemento implementa en su interfaz las llamadas a hasNext() y next(). Con un ArrayList, un for escrito a mano es cerca de 3 veces más rápido, y para las colecciones el for-each será exáctamente equivalente al uso del for con los ArrayList.
    Ejemplos:

    static class Foo {
         int mSplat;
    }
    Foo[] mArray = ...

    public void zero() {
         int sum = 0;
         for (int i = 0; i < mArray.length; ++i) {
             sum += mArray[i].mSplat;
         }
    }

    public void one() {
        int sum = 0;
        Foo[] localArray = mArray;
        int len = localArray.length;

        for (int i = 0; i < len; ++i) {
            sum += localArray[i].mSplat;
        }
    }

    public void two() {
        int sum = 0;
        for (Foo a : mArray) {
            sum += a.mSplat;
        }
    }

    • zero() es la más lenta porque el JIT no puede optimizar el coste de pedir el tamaño del array cada iteración.
    • one() es más rápida. Guarda todo en variables locales, evitando tener que ir a buscarlas fuera del ámbito de la función. Y optimiza la petición del tamaño del Array.
    • two() es la más rápida para dispositivos sin JIT, y distinguiblemente mejor que la one() para los dispositivos con JIT. Usa el for-each introducido en la versión 1.5 del lenguaje de programación Java.


    En definitiva que me tendré que acostumbrar a usar for-each por defecto, salvo excepciones que necesite algo especial en iteraciones con ArrayList.



    JIT: Compilador al vuelo de la máquina virtual Dalvik (Just In Time compiler).

    Texto original y ejemplos via Use Enhanced For Loop Syntax


    sábado, 9 de abril de 2011

    Interfaz gráfica y los servicios

    Como se hace un Service:

    Esta semana hemos visto los servicios en el curso de Android. No me refiero a los aseos, no, me refiero a un tipo de objeto que se ejecuta en background y por definición no tiene interfaz gráfica ni puede modificar nada de ésta.

    public class MyService extends Service {   
        @Override
        public IBinder onBind(Intent arg0) {
            // Este sirve para enlazarse con el Manager de Notificaciones
            // para que otros programas usen este servicio.
            return null;
        }

        @Override
        public void onCreate() {
            super.onCreate();
            Log.d("MyService", "Arrancando el servicio");
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
           
            Log.d("MyService", "Cerrando el servicio");
        }
    }

    Como se hace un Interface:

    También vimos como hacer que nuestro servicio avisara a nuestras Activities del interfaz gráfico de algún cambio, como por ejemplo en las coordenadas del GPS.
    Para el aviso vimos el uso de las interfaces, como esta:

    public interface IMyServiceListener {
        public void updateCurrentLocation(Location loc);
    }

    Una interfaz muy sencilla con un único método a implementar en las clases que la implementen.
    En el servicio además hay que añadir un ArrayList, por ejemplo, de Listeners del tipo IMyServiceListener y un par de métodos públicos para poder añadir y quitar elementos del ArrayList.

    public static void registerLocationListener(IMyServiceListener listener){
        mArrayListeners.add(listener);
    }

    public static void unregisterLocationListener(IMyServiceListener listener){
        mArrayListeners.remove(listener);
    }

    Para implementar la interfaz simplemente sería algo así:

    public class AdvancedList extends ListActivity implements IMyServiceListener {
        private Location mLocation;

        @Override
        public void updateCurrentLocation(Location loc) {
           
            mLocation = loc;
            Log.d("UpdatingLocation", loc.toString());
           
            Toast.makeText(this, "New location: " +
                                    String.valueOf(loc.getLatitude()) + "-" +
                                    String.valueOf(loc.getLongitude()),
                            Toast.LENGTH_LONG);

           
            new getImageTask().execute();
        }

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
         
            // Arrancanco el servicio creado.
            startService(new Intent(this, MyService.class));

            // Añado esta clase como listener del servicio, porque
            // implementa la interfaz.
            MyService.registerLocationListener(this);

            myAdapter = new MyAdapter(this);
            setListAdapter(myAdapter);
        }
    }

    Y cuando desde el servicio se quiera avisar a todos los listeners de algún cambio, podemos crear un método como el siguiente en el servicio y llamarlo cuando se necesite.

    private void notifyListeners(){
        Log.d("NotifyListeners",
                  "Number of listeners " +
                  String.valueOf(mArrayListeners.size()));

        for(int i = 0; i < mArrayListeners.size(); i++){
            mArrayListeners.get(i).updateCurrentLocation(mLocation);
        }
    }

    Problema:

    Bueno, no quería contar toda la clase de ayer pero al final me he liado. Lo que yo quería contar es que aunque implementemos en nuestras Activities la interfaz del listener, cuando el Service llama a ese método de nuestra Activity, la ejecución sigue en la hebra del Service. Por lo cual ni el Toast que he puesto en la implementación del updateCurrentLocation, ni ningún cambio del interfaz gráfico será realizado.

    Por suerte no he tardado mucho en darme cuenta jeje, habrá que ver otra forma de avisar desde el servicio si se quiere modificar el interfaz gráfico.

    martes, 5 de abril de 2011

    Como java.io.Serializable

    Bueno, yo poco a poco sigo con mis pequeños descubrimientos, y quiero pensar que a alguien le puede venir bien encontrarse con posts como este, que cuenta cosas que una vez sabidas parecen obvias.

    Todo empezó porque un compañero del curso de Android se le ocurrió la brillante idea de serializar un objeto para pasarlo de una Activity a otra por medio de Intent. Y claro, yo quise copiarle.
    Como llegué tarde a clase y además no presté atención al profesor (no leas esto Rocapal ;) ), es ahora cuando descubro cómo funciona el interfaz java.io.Serializable.

    Mi clase:

    public class RestaurantItem implements java.io.Serializable{
        public String mName;
        public String mDescription;
        public int mPicture;
        public Location mLocation;
    }

    Y después de un rato de darle vueltas he visto que si la clase tiene un atributo de un tipo no básico como "Location" no se va a poder serializar de forma automática.
    He probado cambiando el Location por dos doubles:

    public double mLatitude;
    public double mLongitude;

    y funcionaba, pero prefiero no serializar a quitar el Location :p

    Por cierto, para quien quiera pasar un objeto serializado en un Intent, simplemente tiene que hacer:

    Intent i = new Intent();
    i.putExtra("ObjetoName", myObject);

    Siendo myObject un objeto de la clase que implementa java.io.Serializable.
    Y para recuperar el objeto del Intent, supuestamente en la otra Activity:

    ObjSerializable obj = (ObjSerializable) i.getSerializableExtra("ObjetoName");

    Y ya está.
    Related Posts Plugin for WordPress, Blogger...