jueves, 5 de mayo de 2011

Principiantes con el teclado

Explicación básica sobre los atajos del teclado.
Fuente: GeekSareSexy

miércoles, 4 de mayo de 2011

Mi Cuaderno Android - controles personalizados

Si queremos dar un toque especial y original a nuestra aplicación, o simplemente si necesitamos cierta funcionalidad no presente en los componentes estándar de Android, nos vemos en la necesidad de crear nuestros propios controles personalizados, diseñados a medida de nuestros requisitos.

Android admite por supuesto crear controles personalizados, y permite hacerlo de diferentes formas:
  1. Extendiendo la funcionalidad de un control ya existente. 
  2. Combinando varios controles para formar un otro más complejo. 
  3. Diseñando desde cero un nuevo control. 
Vamos a ver cómo podemos crear un nuevo control partiendo de la base de un control ya existente. A modo de ejemplo, vamos a extender el control EditText (cuadro de texto) para que muestre en todo momento el número de caracteres que contiene a medida que se escribe en él. Intentaríamos emular algo así como el editor de mensajes SMS del propio sistema operativo, que nos avisa del número de carateres que contiene el mensaje. En nuestro caso, como resultado obtendremos un control como el que se muestra en la siguiente imagen:
Controles Personalizados Android: EditText Extendido
Como vemos, en la esquina superior derecha del cuadro de texto vamos a mostrar el número de caracteres del mensaje de texto introducido, que ira actualizándose a medida que modificamos el texto.

Para empezar, vamos a crear una nueva clase java que extienda del control que queremos utilizar como base, en este caso EditText.

public class ExtendedEditText extends EditText
{
//...
}

Tras esto, sobreescribiremos siempre los tres constructores heredados, donde por el momento nos limitaremos a llamar al mismo constructor de la clase padre.

public ExtendedEditText(Context context, AttributeSet attrs, int defStyle){
    super(context, attrs,defStyle);
}

public ExtendedEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ExtendedEditText(Context context) {
    super(context);
}

Por último el paso más importante. Dado que queremos modificar el aspecto del control para añadir el contador de caracteres tendremos que sobrescribir el evento onDraw(), que es llamado por Android cada vez que hay que redibujar el control en pantalla. Este método recibe como parámetro un objeto Canvas, que no es más que el “lienzo” sobre el que podemos dibujar todos los elementos extra necesarios en el control. El objeto Canvas, proporciona una serie de métodos para dibujar cualquier tipo de elemento (lineas, rectángulos, elipses, texto, bitmaps, …) sobre el espacio ocupado por el control. En nuestro caso tan sólo vamos a necesitar dibujar sobre el control un rectángulo que sirva de fondo para el contador y el texto del contador con el número de caracteres actual del cuadro de texto. No vamos a entrar en muchos detalles sobre la forma de dibujar gráficos ya que ése será tema de otro artículo, pero vamos a ver al menos las acciones principales.
En primer lugar definiremos los “pinceles” (objetos Paint) que utilizaremos para dibujar, uno de ellos (p1) de color negro y relleno sólido para el fondo del contador, y otro (p2) de color blanco para el texto.

Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setColor(Color.BLACK);
p1.setStyle(Style.FILL);
Paint p2 = new Paint(Paint.ANTI_ALIAS_FLAG);
p2.setColor(Color.WHITE);

Dado que estos elementos tan sólo hará falta crearlos la primera vez que se dibuje el control, para evitar trabajo innecesario no incluiremos su definición en el método onDraw(), sino que los definiremos como atributos de la clase y los inicializaremos en el constructor del control (en los tres).

Una vez definidos los diferentes pinceles necesarios, dibujaremos el fondo y el texto del contador mediante los métodos drawRect() y drawText(), respectivamente, del objeto canvas recibido en el evento.

@Override
public void onDraw(Canvas canvas)
{
    //Llamamos al método de la clase base (EditText)
    super.onDraw(canvas);
    //Dibujamos el fondo negro del contador
    canvas.drawRect(this.getWidth()-30, 5,
    this.getWidth()-5, 20, p1);

    //Dibujamos el número de caracteres sobre el contador
    canvas.drawText("" + this.getText().toString().length(),
    this.getWidth()-28, 17, p2);
}
Como puede comprobarse, a estos métodos se les pasará como parámetro las coordenadas del elemento a dibujar relativas al espacio ocupado por el control y el pincel a utilizar en cada caso.

Hecho esto, ya tenemos finalizado nuestro cuadro de texto personalizado con contador de caracteres. Para añadirlo a la interfaz de nuestra aplicación lo incluiremos en el layout XML de la ventana tal como haríamos con cualquier otro control, teniendo en cuenta que deberemos hacer referencia a él con el nombre completo de la nueva clase creada (incluido el paquete java), que en mi caso particular sería net.sgoliver.ExtendedEditText.
<net.sgoliver.ExtendedEditText
android:id="@+id/TxtPrueba"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

creación de controles compuestos
Es decir, controles personalizados construidos a partir de varios controles estándar, combinando la funcionalidad de todos ellos en un sólo control reutilizable en otras aplicaciones.

Como ejemplo ilustrativo vamos a crear un control de identificación (login) formado por varios controles estandar de Android. La idea es conseguir un control como el que se muestra la siguiente imagen esquemática:
Esquema control login android
A efectos didácticos, y para no complicar más el ejemplo, vamos a añadir también a la derecha del botón Login una etiqueta donde mostrar el resultado de la identificación del usuario (login correcto o incorrecto).

A este control añadiremos además eventos personalizados, veremos como añadirlo a nuestras aplicaciones, y haremos que se pueda personalizar su aspecto desde el layout XML de nuestra interfaz utilizando también atributos XML personalizados.

Empecemos por el principio. Lo primero que vamos a hacer es construir la interfaz de nuestro control a partir de controles sencillos: etiquetas, cuadros de texto y botones. Para ello vamos a crear un nuevo layout XML en la carpeta \res\layout con el nombre “control_login.xml“. En este fichero vamos a definir la estructura del control como ya hemos visto en muchos artículos anteriores, sin ninguna particularidad destacable. Para este caso quedaría como sigue:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dip">

<TextView android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Usuario:"
android:textStyle="bold" />

<EditText android:id="@+id/TxtUsuario"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />

<TextView android:id="@+id/TextView02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Contraseña:"
android:textStyle="bold" />

<EditText android:id="@+id/TxtPassword"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />

<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/BtnAceptar"
android:text="Login"
android:paddingLeft="20dip"
android:paddingRight="20dip" />

<TextView android:id="@+id/LblMensaje"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:textStyle="bold" />

</LinearLayout>

</LinearLayout>

Visualmente, nuestro control ya quedaría como se observa en la siguiente imagen:
Control de Login - Diseño principal

A continuación crearemos su clase java asociada donde definiremos toda la funcionalidad de nuestro control. Dado que nos hemos basado en un LinearLayout para construir el control, esta nueva clase deberá heredar también de la clase java LinearLayout de Android. Redefiniremos además los dos constructores básicos:
public class ControlLogin extends LinearLayout
{
    public ControlLogin(Context context) {
        super(context);
        inicializar();
        }
    public ControlLogin(Context context, AttributeSet attrs) {
        super(context, attrs);
        inicializar();
    }
}

Como se puede observar, todo el trabajo lo dejamos para el método inicializar(). En este método inflaremos el layout XML que hemos definido, obtendremos las referencias a todos los controles y asignaremos los eventos necesarios. Todo esto ya lo hemos hecho en otras ocasiones, por lo que tampoco nos vamos a detener mucho. Veamos como queda el método completo:

private void inicializar()
{
    //Utilizamos el layout 'control_login' como interfaz del control
    String infService = Context.LAYOUT_INFLATER_SERVICE;
    LayoutInflater li = (LayoutInflater)getContext().getSystemService(infService);
    li.inflate(R.layout.control_login, this, true);
    //Obtenemoslas referencias a los distintos control
    txtUsuario = (EditText)findViewById(R.id.TxtUsuario);
    txtPassword = (EditText)findViewById(R.id.TxtPassword);
    btnLogin = (Button)findViewById(R.id.BtnAceptar);
    lblMensaje = (TextView)findViewById(R.id.LblMensaje);

    //Asociamos los eventos necesarios
    asignarEventos();
}

Dejaremos por ahora a un lado el método asignarEventos(), volveremos sobre él más tarde.

Con esto ya tenemos definida la interfaz y la funcionalidad básica del nuevo control por lo que ya podemos utilizarlo desde otra actividad como si se tratase de cualquier otro control predefinido. Para ello haremos referencia a él utilizando la ruta completa del paquete java utilizado, en nuestro caso quedaría como net.sgoliver.ControlLogin. Vamos a insertar nuestro nuevo control en la actividad principal de la aplicación:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip" >

<net.sgoliver.ControlLogin android:id="@+id/CtlLogin"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#0000AA" />
</LinearLayout>

Dado que estamos heredando de un LinearLayout podemos utilizar en principio cualquier atributo permitido para dicho tipo de controles, en este caso hemos establecido por ejemplo los atributos layout_width, layout_height y background. Si ejecutamos ahora la aplicación veremos cómo ya hemos conseguido gran parte de nuestro objetivo:
Control de Login - Aplicacion principal 1

Vamos a añadir ahora algo más de funcionalidad. En primer lugar, podemos añadir algún método público exclusivo de nuestro control. Como ejemplo podemos añadir un método que permita modificar el texto de la etiqueta de resultado del login. Esto no tiene ninguna dificultad:

public void setMensaje(String msg)
{
    lblMensaje.setText(msg);
}

En segundo lugar, todo control que se precie debe tener algunos eventos que nos permitan responder a las acciones del usuario de la aplicación. Así por ejemplo, los botones tienen un evento OnClick, las listas un evento OnItemSelected, etc. Pues bien, nosotros vamos a dotar a nuestro control de un evento personalizado, llamado OnLogin, que se lance cuando el usuario introduce sus credenciales de identificación y pulsa el botón “Login”.
Para ello, lo primero que vamos a hacer es concretar los detalles de dicho evento, creando una interfaz java para definir su listener. Esta interfaz tan sólo tendrá un método llamado onLogin() que devolverá los dos datos introducidos por el usuario (usuario y contraseña). Vemos cómo queda:
package net.sgoliver;
public interface OnLoginListener
{
    void onLogin(String usuario, String password);
}

A continuación, deberemos añadir un nuevo miembro de tipo OnLoginListener a la clase ControlLogin, y un método público que permita suscribirse al nuevo evento.
public class ControlLogin extends LinearLayout
{
    private OnLoginListener listener;
    //...
    public void setOnLoginListener(OnLoginListener l)
    {
        listener = l;
    }
}

Con esto, la aplicación principal ya puede suscribirse al evento OnLogin y ejecutar su propio código cuando éste se genere. ¿Pero cuándo se genera exactamente? Dijimos antes que queremos lanzar el evento OnLogin cuando el usuario pulse el botón “Login” de nuestro control. Pues bien, para hacerlo, volvamos al método asignarEventos() que antes dejamos aparcado. En este método vamos a implementar el evento OnClick del botón de Login para lanzar el nuevo evento OnLogin del control. ¿Confundido?. Intento explicarlo de otra forma. Vamos a aprovechar el evento OnClick() del botón Login (que es un evento interno a nuestro control, no se verá desde fuera) para lanzar hacia el exterior el evento OnLogin() (que será el que debe capturar y tratar la aplicación que haga uso del control).
Control de Login - Eventos
Para ello, implementaremos el evento OnClick como ya hemos hecho en otras ocasiones y como acciones generaremos el evento OnLogin de nuestro listener pasándole los datos que ha introducido el usuario en los cuadros de texto “Usuario” y “Contraseña”:

private void asignarEventos()
{
    btnLogin.setOnClickListener(new OnClickListener()
    {
        @Override
        public void onClick(View v) {
            listener.onLogin(txtUsuario.getText().toString(),
            txtPassword.getText().toString());
        }
    });
}

Con todo esto, la aplicación principal ya puede implementar el evento OnLogin de nuestro control, haciendo por ejemplo la validación de las credenciales del usuario y modificando convenientemente el texto de la etiqueta de resultado:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ctlLogin = (ControlLogin)findViewById(R.id.CtlLogin);
    ctlLogin.setOnLoginListener(new OnLoginListener()
    {
        @Override
        public void onLogin(String usuario, String password)
        {
            //Validamos el usuario y la contraseña
            if (usuario.equals("demo") && password.equals("demo"))
                ctlLogin.setMensaje("Login correcto!");
            else
                ctlLogin.setMensaje("Vuelva a intentarlo.");
        }
    });
}

Veamos lo que ocurre al ejecutar ahora la aplicación principal e introducir las credenciales correctas:
Control de Login - Aplicación principal 2
Nuestro control está ya completo, en aspecto y funcionalidad. Hemos personalizado su interfaz y hemos añadido métodos y eventos propios. ¿Podemos hacer algo más? Pues sí.

Cuando vimos cómo añadir el control de login al layout de la aplicación principal dijimos que podíamos utilizar cualquier atributo xml permitido para el contenedor LinearLayout, ya que nuestro control derivaba de éste. Pero vamos a ir más allá y vamos a definir también atributos xml exclusivos para nuestro control. Como ejemplo, vamos a definir un atributo llamado login_text que permita establecer el texto del botón de Login desde el propio layout xml, es decir, en tiempo de diseño.

Primero vamos de declarar el nuevo atributo y lo vamos a asociar al control ControlLogin. Esto debe hacerse en el fichero \res\values\attrs.xml. Para ello, añadiremos una nueva sección <declare-styleable> asociada a ControlLogin dentro del elemento <resources>, donde indicaremos el nombre (name) y el tipo (format) del nuevo atributo.

<resources>
    <declare-styleable name="ControlLogin">
    <attr name="login_text" format="string"/>
    </declare-styleable>
</resources>

En nuestro caso, el tipo del atributo será string, dado que contendrá una cadena de texto con el mensaje a mostrar en el botón.

Con esto ya tendremos permitido el uso del nuevo atributo dentro del layout de la aplicación principal. Ahora nos falta procesar el atributo desde nuestro control personalizado. Este tratamiento lo podemos hacer en el constructor de la clase ControlLogin. Para ello, obtendremos la lista de atributos asociados a ControlLogin mediante el método obtainStyledAttributes() del contexto de la aplicación, obtendremos el valor del nuevo atributo definido (mediante su ID, que estará formado por la concatenación del nombre del control y el nombre del atributo, en nuestro caso “ControlLogin_login_text“) y modificaremos el texto por defecto del control con el nuevo texto.

public ControlLogin(Context context, AttributeSet attrs) {
    super(context, attrs);
    inicializar();

// Procesamos los atributos XML personalizados
    TypedArray a =getContext().obtainStyledAttributes(attrs,
    R.styleable.ControlLogin);
    String textoBoton = a.getString(R.styleable.ControlLogin_login_text);
    btnLogin.setText(textoBoton);
    a.recycle();
}

Ya sólo nos queda utilizarlo. Para ello debemos primero declarar un nuevo espacio de nombres (namespace) local para el paquete java utilizado, que en nuestro caso he llamado “sgo”:

xmlns:sgo="http://schemas.android.com/apk/res/net.sgoliver"

Tras esto, sólo queda asignar el valor del nuevo atributo precedido del nuevo namespace, por ejemplo con el texto “Entrar”:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sgo="http://schemas.android.com/apk/res/net.sgoliver"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip" >

<net.sgoliver.ControlLogin android:id="@+id/CtlLogin"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#0000AA"
sgo:login_text="Entrar" />
</LinearLayout>

Finalmente, si ejecutamos de nuevo la aplicación principal veremos cómo el botón de login se inicializa con el texto definido en el atributo login_text y que todo continúa funcionando correctamente.
Control de Login - Aplicación principal 3
Crear un control desde cero
Sin utilizar como base otros controles existentes. Como ejemplo, vamos a construir un control que nos permita seleccionar un color entre varios disponibles.

Los colores disponibles van a ser sólo cuatro, que se mostrarán en la franja superior del control. En la parte inferior se mostrará el color seleccionado en cada momento, o permanecerá negro si aún no se ha seleccionado ningún color. Valga la siguiente imagen como muestra del aspecto que tendrá nuestro control de selección de color:
control-seleccion-color


Por supuesto este control no tiene mucha utilidad práctica dada su sencillez, pero creo que puede servir como ejemplo para comentar los pasos necesarios para construir cualquier otro control más complejo. Empecemos.

En las anteriores ocasiones vimos cómo el nuevo control creado siempre heredaba de algún otro control o contenedor ya existente. En este caso sin embargo, vamos a heredar nuestro control directamente de la clase View (clase padre de la gran mayoría de elementos visuales de Android). Esto implica, entre otras cosas, que por defecto nuestro control no va a tener ningún tipo de interfaz gráfica, por lo que todo el trabajo de “dibujar” la interfaz lo vamos a tener que hacer nosotros. Además, como paso previo a la representación gráfica de la interfaz, también vamos a tener que determinar las dimensiones que nuestro control tendrá dentro de su elemento contenedor. Como veremos ahora, ambas cosas se llevarán a cabo redefiniendo dos eventos de la clase View, onDraw() para el dibujo de la interfaz, y onMeasure() para el cálculo de las dimensiones.

Por llevar un orden cronológico, empecemos comentando el evento onMeasure(). Este evento se ejecuta automáticamente cada vez que se necesita recalcular el tamaño de un control. Pero como ya hemos visto en varias ocasiones, los elementos gráficos incluidos en una aplicación Android se distribuyen por la pantalla de una forma u otra dependiendo del tipo de contenedor o layout utilizado. Por tanto, el tamaño de un control determinado en la pantalla no dependerá sólo de él, sino de ciertas restricciones impuestas por su elemento contenedor o elemento padre. Para resolver esto, en el evento onMeasure() recibiremos como parámetros las restricciones del elemento padre en cuanto a ancho y alto del control, con lo que podremos tenerlas en cuenta a la hora de determinar el ancho y alto de nuestro control personalizado. Estas restricciones se reciben en forma de objetos MeasureSpec, que contiene dos campos: modo y tamaño. El significado del segundo de ellos es obvio, el primero por su parte sirve para matizar el significado del segundo. Me explico. Este campo modo puede contener tres valores posibles:
  • AT_MOST: indica que el control podrá tener como máximo el tamaño especificado. 
  • EXACTLY: indica que al control se le dará exactamente el tamaño especificado. 
  • UNSPECIFIED: indica que el control padre no impone ninguna restricción sobre el tamaño. 
Dependiendo de esta pareja de datos, podremos calcular el tamaño deseado para nuestro control. Para nuestro control de ejemplo, apuraremos siempre el tamaño máximo disponible (o un tamaño por defecto de 200*100 en caso de no recibir ninguna restricción), por lo que en todos los casos elegiremos como tamaño de nuestro control el tamaño recibido como parámetro:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    int ancho = calcularAncho(widthMeasureSpec);
    int alto = calcularAlto(heightMeasureSpec);
    setMeasuredDimension(ancho, alto);
}

private int calcularAlto(int limitesSpec)
{
    int res = 100; //Alto por defecto
    int modo = MeasureSpec.getMode(limitesSpec);
    int limite = MeasureSpec.getSize(limitesSpec);
    if (modo == MeasureSpec.AT_MOST) {
        res = limite;
    }
    else if (modo == MeasureSpec.EXACTLY) {
        res = limite;
    }
return res;
}

private int calcularAncho(int limitesSpec)
{
    int res = 200; //Ancho por defecto
    int modo = MeasureSpec.getMode(limitesSpec);
    int limite = MeasureSpec.getSize(limitesSpec);
    if (modo == MeasureSpec.AT_MOST) {
        res = limite;
    }
    else if (modo == MeasureSpec.EXACTLY) {
        res = limite;
    }
return res;
}

Como nota importante, al final del evento onMeasure() siempre debemos llamar al método setMeasuredDimension() pasando como parámetros el ancho y alto calculados para nuestro control.

Con esto ya hemos determinado las dimensiones del control, por lo que tan sólo nos queda dibujar su interfaz gráfica. Como hemos indicado antes, esta tarea se realiza dentro del evento onDraw(). Este evento recibe como parámetro un objeto de tipo Canvas, sobre el que podremos ejecutar todas las operaciones de dibujo de la interfaz. No voy a entrar en detalles de la clase Canvas, porque ése será tema central de un próximo artículo. Por ahora nos vamos a conformar sabiendo que es la clase que contiene la mayor parte de los métodos de dibujo en interfaces Android, por ejemplo drawRect() para dibujar rectángulos, drawCircle() para círculos, drawBitmap() para imagenes, drawText() para texto, e infinidad de posibilidades más. Para consultar todos los métodos disponibles puedes dirigirte a la documentación oficial de la clase Canvas de Android. Además de la clase Canvas, también me gustaría destacar la clase Paint, que permite definir el estilo de dibujo a utilizar en los metodos de dibujo de Canvas, por ejemplo el ancho de trazado de las líneas, los colores de relleno, etc.

Para nuestro ejemplo no necesitaríamos conocer nada más, ya que la interfaz del control es relativamente sencilla. Vemos primero el código y después comentamos los pasos realizados:
@Override
protected void onDraw(Canvas canvas)
{
    //Obtenemos las dimensiones del control
    int alto = getMeasuredHeight();
    int ancho = getMeasuredWidth();
    //Colores Disponibles
    Paint pRelleno = new Paint();
    pRelleno.setStyle(Style.FILL);
    pRelleno.setColor(Color.RED);
    canvas.drawRect(0, 0, ancho/4, alto/2, pRelleno);
    pRelleno.setColor(Color.GREEN);
    canvas.drawRect(ancho/4, 0, 2*(ancho/4), alto/2, pRelleno);
    pRelleno.setColor(Color.BLUE);
    canvas.drawRect(2*(ancho/4), 0, 3*(ancho/4), alto/2, pRelleno);
    pRelleno.setColor(Color.YELLOW);
    canvas.drawRect(3*(ancho/4), 0, 4*(ancho/4), alto/2, pRelleno);
    //Color Seleccionado
    pRelleno.setColor(colorSeleccionado);
    canvas.drawRect(0, alto/2, ancho, alto, pRelleno);
    //Marco del control
    Paint pBorde = new Paint();
    pBorde.setStyle(Style.STROKE);
    pBorde.setColor(Color.WHITE);
    canvas.drawRect(0, 0, ancho-1, alto/2, pBorde);
    canvas.drawRect(0, 0, ancho-1, alto-1, pBorde);
}

En primer lugar obtenemos las dimensiones calculadas en la última llamada a onMeasure() mediante los métodos getMeasuredHeight() y getMeasuredWidth(). Posteriormente definimos un objeto Paint que usaremos para dibujar los rellenos de cada color seleccionable. Para indicar que se trata del color de relleno a utilizar utilizaremos la llamada a setStyle(Style.FILL). Tras esto, ya sólo debemos dibujar cada uno de los cuadros en su posición correspondiente con drawRect(), estableciendo antes de cada uno de ellos el color deseado con setColor(). Por último, dibujamos el marco del control definiendo un nuevo objeto Paint, esta vez con estilo Style.STROKE dado que se utilizará para dibujar sólo líneas, no rellenos.

Con esto, ya deberíamos tener un control con el aspecto exacto que definimos en un principio. El siguiente paso será definir su funcionalidad implementando los eventos a los que queramos que responda nuestro control, tanto eventos internos como externos.

En nuestro caso sólo vamos a tener un evento de cada tipo. En primer lugar definiremos un evento interno (evento que sólo queremos capturar de forma interna al control, sin exponerlo al usuario) para responder a las pulsaciones del usuario sobre los colores de la zona superior, y que utilizaremos para actualizar el color de la zona inferior con el color seleccionado. Para ello implementaremos el evento onTouch(), lanzado cada vez que el usuario toca la pantalla sobre nuestro control. La lógica será sencilla, simplemente consultaremos las coordenadas donde ha pulsado el usuario (mediante los métodos getX() y getY()), y dependiendo del lugar pulsado determinaremos el color sobre el que se ha seleccionado y actualizaremos el valor del atributo colorSeleccionado. Finalmente, llamamos al método invalidate() para refrescar la interfaz del control, reflejando así el cambio en el color seleccionado, si se ha producido.

@Override
public boolean onTouchEvent(MotionEvent event)
{
    //Si se ha pulsado en la zona superior
    if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
    {
        //Si se ha pulsado dentro de los límites del control
        if (event.getX() > 0 && event.getX() < getMeasuredWidth())
        {
            //Determinamos el color seleccionado según el punto pulsado
            if(event.getX() > (getMeasuredWidth()/4)*3)
                colorSeleccionado = Color.YELLOW;
            else if(event.getX() > (getMeasuredWidth()/4)*2)
                colorSeleccionado = Color.BLUE;
            else if(event.getX() > (getMeasuredWidth()/4)*1)
                colorSeleccionado = Color.GREEN;
            else
                colorSeleccionado = Color.RED;
        //Refrescamos el control
        this.invalidate();
        }
    }
return super.onTouchEvent(event);
}

En segundo lugar crearemos un evento externo personalizado, que lanzaremos cuando el usuario pulse sobre la zona inferior del control, como una forma de aceptar definitivamente el color seleccionado. Llamaremos a este evento onSelectedColor(). Primero definiremos una interfaz para el listener de nuestro evento:

package net.sgoliver.android;
public interface OnColorSelectedListener
{
    void onColorSelected(int color);
}

Posteriormente, definiremos un objeto de este tipo como atributo de nuestro control y escribiremos un nuevo método que permita a las aplicaciones suscribirse al evento:

public class SelectorColor extends View
{
    private OnColorSelectedListener listener;
    //...
    public void setOnColorSelectedListener(OnColorSelectedListener l)
    {
        listener = l;
    }
}

Y ya sólo nos quedaría lanzar el evento en el momento preciso. Esto también lo haremos dentro del evento onTouch(), cuando detectemos que el usuario ha pulsado en la zona inferior de nuestro control:

@Override
public boolean onTouchEvent(MotionEvent event)
{
    //Si se ha pulsado en la zona superior
    if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
    {
        //...
    }
    //Si se ha pulsado en la zona inferior
    else if (event.getY() > (getMeasuredHeight()/2) && event.getY() < getMeasuredHeight())
    {
        //Lanzamos el evento externo de selección de color
        listener.onColorSelected(colorSeleccionado);
    }
return super.onTouchEvent(event);
}
Con esto, nuestra aplicación principal ya podría suscribirse a este nuevo evento para estar informada cada vez que se seleccione un color. Sirva la siguiente plantilla a modo de ejemplo:
public class ControlPersonalizado extends Activity
{
    private SelectorColor ctlColor;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ctlColor = (SelectorColor)findViewById(R.id.scColor);
        ctlColor.setOnColorSelectedListener(new OnColorSelectedListener()
        {
        @Override
                public void onColorSelected(int color)
                {
                    //Aquí se trataría el color seleccionado (parámetro 'color'
                    //...
                }
        });
    }
}

Con esto, tendríamos finalizado nuestro control completamente personalizado, que hemos construido sin utilizar como base ningún otro control predefinido, definiendo desde cero tanto su aspecto visual como su funcionalidad interna o sus eventos públicos.

sgoliver.net

martes, 3 de mayo de 2011

Bloquear envío de formularios con JQuery


No es mala idea realizar una comprobación de los datos introducidos en un formulario antes de enviarlo. Probemos con un ejemplo, en el siguiente formulario.

<form id="formulario" action="post.php">
        <label for="valor">Inserta un valor: </label>
        <input id="valor" type="text" size="10">
        <input type="submit" value="Enviar"/>
</form>

Ahora comenzaremos con el código javaScript. Realizando las comprobación oportunas en este caso comprobamos que en la caja de texto se haya introducido la palabra "enviar".

$(document).ready(function(){
        $("#formulario").submit(function() {
            var x = $("#valor").val();
            if (x!="enviar") {
                alert("Valor introducido no válido");
            return false; }
        return true; });
});

lunes, 2 de mayo de 2011

Flujos visuales en las interfaces


El flujo visual es el camino que recorren los ojos a través de una pantalla. Si el diseño está bien realizado este movimiento es natural y fluido, el ojo va de un objeto al siguiente sin darse cuenta, de forma clara. 

El diseñador tiene que presentar los elementos o la información según se desee que los vea o lea el usuario. Y debe dirigir la mirada hacia el objetivo buscado.

Para ello se pueden utilizar diferentes variables o atributos visuales de los elementos, como flechas, caras, diferentes tamaños, perspectiva, secuencia de textos o de imágenes, etc. Una variable importante a tener en cuenta e la hora de dirigir el flujo visual es la localización del contenido o los elementos. 

El orden convencional en Occidente, como se sabe, es de arriba hacia abajo y de izquierda a derecha. Este es el orden que debe seguir la presentación de los contenido.

No es esto lo que se hace en la pantalla inferior de un kiosco interactivo, en la que el usuario, una vez seleccionadas las fotografías que desea imprimir ha de buscar el próximo paso, no se le ofrece o facilita claramente la acción siguiente.



El ojo tiene cierta resistencia en desplazarse hacia arriba o hacia la izquierda en la pantalla. Por lo que romper esta lógica exige más esfuerzo al usuario y es menos eficiente.

Sin embargo, en la pantalla siguiente, se ofrece una dirección más clara, una mejor secuencia para navegar por sus contenidos.


El objetivo que deseamos que alcance, la llamada a la acción se le ofrece al usuario, no lo tiene que buscar, está justo después de los elementos que ha seleccionado, en parta inferior derecha. En el desenlace de la acción.

domingo, 1 de mayo de 2011

Google y sus aplicaciones para mejorar la descarga de sitios web


Google tiene una gama de aplicaciones para la optimización de sitios web. Entre ellas podemos ver Page Speed Online. Una herramienta web que permite a los usuarios analizar las páginas en cualquier momento, en cualquier navegador, sin necesidad de descargar una extensión. Analiza el contenido de una página web, a continuación, genera sugerencias para hacer que la página se descargue más rápidamente. También cuenta con Page Speed ​​Móvil, que permite a los desarrolladores analizar su sitio según las especificaciones de un navegador móvil, y recibe sugerencias específicas para la optimización de móviles.

Las sugerencias se dividen en tres grados de prioridad y en una escala de 0 a 100. Evalúan desde el punto de vista del usuario, generalmente medido como el tiempo de carga de la página. Las sugerencias se agrupan en seis categorías que cubren diferentes aspectos de la optimización de carga de la página:
  • Optimizar el almacenamiento en la caché.
  • Minimizar los tiempos de respuesta.
  • Minimizar el gasto de la petición - la reducción de tamaño de subida.
  • Minimizar el tamaño de la carga útil.
  • Optimización de la prestación del navegador.
  • Optimización de móviles.
Las extensiones de Page Speed para navegadores, disponibles para Chrome y Firefox, son la manera más rápida para obtener análisis precisos de rendimiento de nuestras páginas web y obtener sugerencias específicas sobre cómo optimizarlos.

Para los administradores de un Apache™ HTTP Server 2 tiene implementado mod_pagespeed, que mejora el rendimiento de la página de velocidad de forma automática. Los webmasters pueden obtener los beneficios de las optimizaciones manuales sin tener que arreglar los problemas de rendimiento que detecta Page Speed.

A parte de las herramientas que nos ofrece google tambien existen las clasicas GTmetrix o Slowcop.

domingo, 3 de abril de 2011

Comunicado - Sobrecarga cerebral


    Apenas tengo tiempo disponible para dedicar al blog, quiero escribir muchas publicaciones mas y tengo nuevas ideas para hacer publicas, pero se me acabó el tiempo libre. Tranquilos que no durará mucho, aunque estaré unas semanas sin publicar, no os preocupéis por que es un bache temporal. Tengo muchas cosas a las que me debo dedicar: la universidad, el trabajo... tengo proyectos y trabajos de los cuales algunos se han tenido que retrasar y no quiero que se sigan aplazando.

Por otro lado comentaros que muchas gracias a los que me visitáis, espero que os haya servido de ayuda o punto de partida alguno de los post que he ido publicando.

Muchas gracias a todos, nos vemos ;)

sábado, 2 de abril de 2011

Conjunto de herramientas Online

Herramientas para gestionar, hacer analítica web, controlar la social media en los diferentes perfiles sociales, analizar tu reputación online, mejorar el posicionamiento, optimizar tu código, generar keyphrases…

Búsqueda
15 recursos para búsqueda de información
10 herramientas para buscar personas en redes sociales
6 herramientas para Social Media
5 herramientas para filtrar contenidos en blogs, Facebook y Twitter

Educación
100 herramientas para profesores y alumnos

Facebook
10 aplicaciones para tu página
7 buscadores
6 herramientas para analizar una marca
5 herramientas

Fotografías
25 bancos de imágenes gratuitos
8 recursos para acceder a imágenes gratuitas

Gestión del tiempo
6 herramientas para organizar y administrar tu tiempo

Monitorización
100 herramientas para monitorizar la web social
34 herramientas de gestión y monitorización
10 herramientas para gestionar tu reputación
10 herramientas para gestionar nuestra presencia en redes sociales
8 herramientas para manejar tu reputación online

Publicación
12 recursos para cobertura en tiempo real

SEO
9 herramientas SEO
3 herramientas para SEO

Trabajo en equipo
Más de 50 herramientas para trabajar en equipo a través de la web [artículo en inglés]
Herramientas para comunicarse por Internet (texto, vídeo y audio)
5 herramientas colaborativas para trabajar en la nube
Guía para crear, gestionar y compartir documentos en la web

Twitter
205 aplicaciones
150 aplicaciones para sacarle partido
54 aplicaciones para realizar búsquedas
13 herramientas para gestionar tus seguidores
8 herramientas para compartir vídeos
3 alternativas a Klout para medir tu influencia
2 herramientas de visualización [artículo en inglés]
Herramienta para hacer encuestas
Herramienta para enviar un mensaje de más de 140 caracteres

Vídeo
10 herramientas online para edición

Varios
80 herramientas para aumentar tu productividad
40 herramientas para Social Media
32 herramientas de Google para empresas de Ecommerce[artículo en inglés]
31 herramientas para redes sociales
30 herramientas para redes sociales
+ de 25 herramientas para la web 2.0
Herramientas y recursos para blogueros
Buscador de iconos gratuitos
Iconos que no aparecen en el teclado


Fuente: SEOprofesional

viernes, 1 de abril de 2011

Generador de claves aleatorias con php

Si en algún momento nos vemos obligados a tener que generar un conjunto de caracteres alfanuméricos de forma aleatoria por ejemplo una contraseña aleatoria primero necesitaremos crear dos variables.

$caracteres='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$longpalabra=8;

Después generamos un bucle en el cual obtendremos en cada ciclo el carácter aleatorio de nuestra cadena. Finalizando cuando completa la longitud de la palabra deseada.

for($pass='', $n=strlen($caracteres)-1; strlen($pass) < $longpalabra ; ) {
    $x = rand(0,$n);
    $pass.= $caracteres[$x];
}

El resultado final sería el siguiente
print 'Palabra obtenida: ' . $pass;

jueves, 31 de marzo de 2011

Mi Cuaderno Android - controles de selección

Al igual que en otros frameworks Android dispone de diversos controles que nos permiten seleccionar una opción dentro de una lista de posibilidades. Así, podremos utilizar listas desplegables (Spinner), listas fijas (ListView), tablas (GridView) y otros controles específicos de la plataforma como por ejemplo las galerías de imágenes (Gallery).

Adaptadores en Android (adapters)
Para los desarrolladores de java que hayan utilizado frameworks de interfaz gráfica como Swing, el concepto de adaptador les resultará familiar. Un adaptador representa algo así como una interfaz común al modelo de datos que existe por detrás de todos los controles de selección que hemos comentado. Dicho de otra forma, todos los controles de selección accederán a los datos que contienen a través de un adaptador.

Además de proveer de datos a los controles visuales, el adaptador también será responsable de generar a partir de estos datos las vistas específicas que se mostrarán dentro del control de selección. Por ejemplo, si cada elemento de una lista estuviera formado a su vez por una imagen y varias etiquetas, el responsable de generar y establecer el contenido de todos estos “sub-elementos” a partir de los datos será el propio adaptador.

Android proporciona de serie varios tipos de adaptadores sencillos, aunque podemos extender su funcionalidad fácilmente para adaptarlos a nuestras necesidades. Los más comunes son los siguientes:
  • ArrayAdapter. Es el más sencillo de todos los adaptadores, y provee de datos a un control de selección a partir de un array de objetos de cualquier tipo. 
  • SimpleAdapter. Se utiliza para mapear datos sobre los diferentes controles definidos en un fichero XML de layout. 
  • SimpleCursorAdapter. Se utiliza para mapear las columnas de un cursor sobre los diferentes elementos visuales contenidos en el control de selección.
Por ahora nos vamos a conformar con describir la forma de utilizar un ArrayAdapter con los diferentes controles de selección disponibles.
Veamos cómo crear un adaptador de tipo ArrayAdapter para trabajar con un array genérico de java:

final String[] datos = new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};

ArrayAdapter<String> adaptador =
new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, datos);

Comentemos un poco el código. Sobre la primera línea no hay nada que decir, es tan sólo la definición del array java que contendrá los datos a mostrar en el control, en este caso un array sencillo con cinco cadenas de caracteres. En la segunda línea creamos el adaptador en sí, al que pasamos 3 parámetros:
  1. El contexto, que normalmente será simplemente una referencia a la actividad donde se crea el adaptador. 
  2. El ID del layout sobre el que se mostrarán los datos del control. En este caso le pasamos el ID de un layout predefinido en Android (android.R.layout.simple_spinner_item), formado únicamente por un control TextView, pero podríamos pasarle el ID de cualquier layout de nuestro proyecto con cualquier estructura y conjunto de controles, más adelante veremos cómo. 
  3. El array que contiene los datos a mostrar.
Con esto ya tendríamos creado nuestro adaptador para los datos a mostrar y ya tan sólo nos quedaría asignar este adaptador a nuestro control de selección para que éste mostrase los datos en la aplicación.

Control Spinner [API]
Las listas desplegables en Android se llaman Spinner. Funcionan de forma similar al de cualquier control de este tipo, el usuario selecciona la lista, se muestra una especie de lista emergente al usuario con todas las opciones disponibles y al seleccionarse una de ellas ésta queda fijada en el control. Para añadir una lista de este tipo a nuestra aplicación podemos utilizar el código siguiente:

<Spinner android:id="@+id/CmbOpciones"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

Poco vamos a comentar de aquí ya que lo que nos interesan realmente son los datos a mostrar. En cualquier caso, las opciones para personalizar el aspecto visual del control (fondo, color y tamaño de fuente, …) son las mismas ya comentadas para los controles básicos.

Para enlazar nuestro adaptador (y por tanto nuestros datos) a este control utilizaremos el siguiente código java:

final Spinner cmbOpciones = (Spinner)findViewById(R.id.CmbOpciones);
adaptador.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
cmbOpciones.setAdapter(adaptador);

Comenzamos como siempre por obtener una referencia al control a través de su ID. Y en la última línea asignamos el adaptador al control mediante el método setAdapter(). La segunda linea como ya se sabe le pasamos uno de los parámetros ID del layout que tan sólo se aplicará al elemento seleccionado en la lista, es decir, al que se muestra directamente sobre el propio control cuando no está desplegado. Sin embargo, antes indicamos que el funcionamiento normal del control Spinner incluye entre otras cosas mostrar una lista emergente con todas las opciones disponibles. Pues bien, para personalizar también el aspecto de cada elemento en dicha lista emergente tenemos el método setDropDownViewResource(ID_layout), al que podemos pasar otro ID de un layout distinto al primero sobre el que se mostrarán los elementos de la lista emergente. En este caso hemos utilizado otro layout predefinido en Android para las listas desplegables (android.R.layout.simple_spinner_dropdown_item).

Con estas simples lineas de código conseguiremos mostrar un control como el que vemos en las siguientes imágenes:











Como se puede observar en las imágenes, la representación del elemento seleccionado (primera imagen) y el de las opciones disponibles (segunda imagen) es distinto, incluyendo el segundo de ellos incluso algún elemento gráfico a la derecha para mostrar el estado de cada opción. Como hemos comentado, esto es debido a la utilización de dos layouts diferentes para uno y otros elementos.

En cuanto a los eventos lanzados por el control Spinner, el más comunmente utilizado será el generado al seleccionarse una opción de la lista desplegable, onItemSelected. Para capturar este evento se procederá de forma similar a lo ya visto para otros controles anteriormente, asignadole su controlador mediante el método setOnItemSelectedListener():

cmbOpciones.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    public void onItemSelected(AdapterView<?> parent,
        android.view.View v, int position, long id) {
            lblMensaje.setText("Seleccionado: " + datos[position]);
        }

        public void onNothingSelected(AdapterView<?> parent) {
            lblMensaje.setText("");
        }
    });

Para este evento definimos dos métodos, el primero de ellos (onItemSelected) que será llamado cada vez que se selecciones una opción en la lista desplegable, y el segundo (onNothingSelected) que se llamará cuando no haya ninguna opción seleccionada (esto puede ocurrir por ejemplo si el adaptador no tiene datos).
El control ListView muestra al usuario una lista de opciones seleccionables directamente sobre el propio control, sin listas emergentes como en el caso del control Spinner. En caso de existir más opciones de las que se pueden mostrar sobre el control se podrá por supuesto hacer scroll sobre la lista para acceder al resto de elementos.

Para empezar, veamos como podemos añadir un control ListView a nuestra interfaz de usuario:

<ListView android:id="@+id/LstOpciones"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Una vez más, podremos modificar el aspecto del control utilizando las propiedades de fuente y color ya comentadas. Por su parte, para enlazar los datos con el control podemos utlizar por ejemplo el mismo código que para el control Spinner. Definiremos primero un array con nuestros datos de prueba, crearemos posteriormente el adaptador de tipo ArrayAdapter y lo asignaremos finalmente al control mediante el método setAdapter():

final String[] datos =
    new String[]{"Elem1","Elem2","Elem3","Elem4","Elem5"};

ArrayAdapter<String> adaptador =
    new ArrayAdapter<String>(this,
    android.R.layout.simple_list_item_1, datos);

ListView lstOpciones = (ListView)findViewById(R.id.LstOpciones);

lstOpciones.setAdapter(adaptador);

En este caso, para mostrar los datos de cada elemento hemos utilizado otro layout genérico de Android para los controles de tipo ListView (android.R.layout.simple_list_item_1), formado únicamente por un TextView con unas dimensiones determinadas. La lista creada quedaría como se muestra en la imagen siguiente:

Como podéis comprobar el uso básico del control ListView es completamente análogo al ya comentado para el control Spinner.

Si quisiéramos realizar cualquier acción al pulsarse sobre un elemento de la lista creada tendremos que implementar el evento onItemClick. Veamos cómo con un ejemplo:

lstOpciones.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> a, View v, int position, long id) {
    //Acciones necesarias al hacer click
    }
});

Hasta aquí todo sencillo. Pero, ¿y si necesitamos mostrar datos más complejos en la lista? ¿qué ocurre si necesitamos que cada elemento de la lista esté formado a su vez por varios elementos? Pues vamos a provechar los ListView para conseguirlo, aunque todo lo que comentaré es extensible a otros controles de selección.
Para no complicar mucho el tema vamos a hacer que cada elemento de la lista muestre por ejemplo dos líneas de texto a modo de título y subtítulo con formatos diferentes (por supuesto se podrían añadir muchos más elementos, por ejemplo imágenes, checkboxes, etc).
En primer lugar vamos a crear una nueva clase java para contener nuestros datos de prueba. Vamos a llamarla Titular y tan sólo va a contener dos atributos, título y subtítulo.
package net.sgoliver;

public class Titular
{
    private String titulo;
    private String subtitulo;

    public Titular(String tit, String sub){
        titulo = tit;
        subtitulo = sub;
    }

    public String getTitulo(){
        return titulo;
    }

    public String getSubtitulo(){
        return subtitulo;
    }
}

En cada elemento de la lista queremos mostrar ambos datos, por lo que el siguiente paso será crear un layout XML con la estructura que deseemos. En mi caso voy a mostrarlos en dos etiquetas de texto (TextView), la primera de ellas en negrita y con un tamaño de letra un poco mayor. Llamaremos a este layout “listitem_titular.xml“:
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
android:orientation="vertical">

<TextView android:id="@+id/LblTitulo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textStyle="bold"
android:textSize="20px" />

<TextView android:id="@+id/LblSubTitulo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textStyle="normal"
android:textSize="12px" />

</LinearLayout>


Ahora que ya tenemos creados tanto el soporte para nuestros datos como el layout que necesitamos para visualizarlos, lo siguiente que debemos hacer será indicarle al adaptador cómo debe utilizar ambas cosas para generar nuestra interfaz de usuario final. Para ello vamos a crear nuestro propio adaptador extendiendo de la clase ArrayAdapter.

class AdaptadorTitulares extends ArrayAdapter {

Activity context;

    AdaptadorTitulares(Activity context) {
        super(context, R.layout.listitem_titular, datos);
        this.context = context;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = context.getLayoutInflater();
        View item = inflater.inflate(R.layout.listitem_titular, null);

        TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
        lblTitulo.setText(datos[position].getTitulo());

        TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
        lblSubtitulo.setText(datos[position].getSubtitulo());

        return(item);
    }
}

Analicemos el código anterior. Lo primero que encontramos es el constructor para nuestro adaptador, al que sólo pasaremos el contexto (que será la actividad desde la que se crea el adaptador). En este constructor tan sólo guardaremos el contexto para nuestro uso posterior y llamaremos al constructor padre tal como ya vimos al principio de este artículo, pasándole el ID del layout que queremos utilizar (en nuestro caso el nuevo que hemos creado, “listitem_titular”) y el array que contiene los datos a mostrar.

Posteriormente, redefinimos el método encargado de generar y rellenar con nuestros datos todos los controles necesarios de la interfaz gráfica de cada elemento de la lista. Este método es getView().

El método getView() se llamará cada vez que haya que mostrar un elemento de la lista. Lo primero que debe hacer es “inflar” el layout XML que hemos creado. Esto consiste en consultar el XML de nuestro layout y crear e inicializar la estructura de objetos java equivalente. Para ello, crearemos un nuevo objeto LayoutInflater y generaremos la estructura de objetos mediante su método inflate(id_layout).

Tras esto, tan sólo tendremos que obtener la referencia a cada una de nuestras etiquetas como siempre lo hemos hecho y asignar su texto correspondiente según los datos de nuestro array y la posición del elemento actual (parámetro position del método getView()).

Una vez tenemos definido el comportamiento de nuestro adaptador la forma de proceder en la actividad principal será análoga a lo ya comentado, definiremos el array de datos de prueba, crearemos el adaptador y lo asignaremos al control mediante setAdapter():
private Titular[] datos =
    new Titular[]{
        new Titular("Título 1", "Subtítulo largo 1"),
        new Titular("Título 2", "Subtítulo largo 2"),
        new Titular("Título 3", "Subtítulo largo 3"),
        new Titular("Título 4", "Subtítulo largo 4"),
        new Titular("Título 5", "Subtítulo largo 5")};
//...
//...
AdaptadorTitulares adaptador =
    new AdaptadorTitulares(this);

ListView lstOpciones = (ListView)findViewById(R.id.LstOpciones);

lstOpciones.setAdapter(adaptador);

Hecho esto, y si todo ha ido bien, nuestra nueva lista debería quedar como vemos en la imagen siguiente:

Aunque ya sabemos utilizar y personalizar las listas en Android, en el próximo artículo daremos algunas indicaciones para utilizar de una forma mucho más eficiente los controles de este tipo, algo que los usuarios de nuestra aplicación agradecerán enormemente al mejorarse la respuesta de la aplicación y reducirse el consumo de batería.

Cuando comentamos cómo crear nuestro propio adaptador, extendiendo de ArrayAdapter, para personalizar la forma en que nuestros datos se iban a mostrar en la lista escribimos el siguiente código: 

class AdaptadorTitulares extends ArrayAdapter {
    Activity context;

    AdaptadorTitulares(Activity context) {
        super(context, R.layout.listitem_titular, datos);
        this.context = context;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = context.getLayoutInflater();
        View item = inflater.inflate(R.layout.listitem_titular, null);

        TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
        lblTitulo.setText(datos[position].getTitulo());

        TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
        lblSubtitulo.setText(datos[position].getSubtitulo());

        return(item);
    }
}

Centrándonos en la definición del método getView() vimos que la forma normal de proceder consistía en primer lugar en “inflar” nuestro layout XML personalizado para crear todos los objetos correspondientes (con la estructura descrita en el XML) y posteriormente acceder a dichos objetos para modificar sus propiedades. Sin embargo, hay que tener en cuenta que esto se hace todas y cada una de las veces que se necesita mostrar un elemento de la lista en pantalla, se haya mostrado ya o no con anterioridad, ya que Android no “guarda” los elementos de la lista que desaparecen de pantalla (por ejemplo al hacer scroll sobre la lista). El efecto de esto es obvio, dependiendo del tamaño de la lista y sobre todo de la complejidad del layout que hayamos definido esto puede suponer la creación y destrucción de cantidades ingentes de objetos (que puede que ni siquiera nos sean necesarios), es decir, que la acción de inflar un layout XML puede ser bastante costosa, lo que podría aumentar mucho, y sin necesidad, el uso de CPU, de memoria, y de batería.
Para aliviar este problema, Android nos propone un método que permite reutilizar algún layout que ya hayamos inflado con anterioridad y que ya no nos haga falta por algún motivo, por ejemplo porque el elemento correspondiente de la lista ha desaparecido de la pantalla al hacer scroll. De esta forma evitamos todo el trabajo de crear y estructurar todos los objetos asociados al layout, por lo que tan sólo nos quedaría obtener la referencia a ellos mediante findViewById() y modificar sus propiedades.

¿Pero cómo podemos reutilizar estos layouts “obsoletos”? Pues es bien sencillo, siempre que exista algún layout que pueda ser reutilizado éste se va a recibir a través del parámetro convertView del método getView(). De esta forma, en los casos en que éste no sea null podremos obviar el trabajo de inflar el layout. Veamos cómo quedaría el métod getView() tras esta optimización:

public View getView(int position, View convertView, ViewGroup parent)
{
    View item = convertView;

    if(item == null)
    {
        LayoutInflater inflater = context.getLayoutInflater();
        item = inflater.inflate(R.layout.listitem_titular, null);
    }

    TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
    lblTitulo.setText(datos[position].getTitulo());

    TextView lblSubtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
    lblSubtitulo.setText(datos[position].getSubtitulo());

    return(item);
}

Si añadimos más elementos a la lista y ejecutamos ahora la aplicación podemos comprobar que al hacer scroll sobre la lista todo sigue funcionando con normalidad, con la diferencia de que le estamos ahorrando gran cantidad de trabajo a la CPU.

Pero vamos a ir un poco más allá. Con la optimización que acabamos de implementar conseguimos ahorrarnos el trabajo de inflar el layout definido cada vez que se muestra un nuevo elemento. Pero aún hay otras dos llamadas relativamente costosas que se siguen ejecutando en todas las llamadas. Me refiero a la obtención de la referencia a cada uno de los objetos a modificar mediante el método findViewById(). La búsqueda por ID de un control determinado dentro del árbol de objetos de un layout también puede ser una tarea costosa dependiendo de la complejidad del propio layout.¿Por qué no aprovechamos que estamos “guardando” un layout anterior para guardar también la referencia a los controles que lo forman de forma que no tengamos que volver a buscarlos? Pues eso es exactamente lo que vamos a hacer mediante lo que en los ejemplos de Android llaman un ViewHolder. La clase ViewHolder tan sólo va a contener una referencia a cada uno de los controles que tengamos que manipular de nuestro layout, en nuestro caso las dos etiquetas de texto. Definamos por tanto esta clase de la siguiente forma:
static class ViewHolder {
    TextView titulo;
    TextView subtitulo;
}

La idea será por tanto crear e inicializar el objeto ViewHolder la primera vez que inflemos un elemento de la lista y asociarlo a dicho elemento de forma que posteriormente podamos recuperarlo fácilmente. ¿Pero dónde lo guardamos? Fácil, en Android todos los controles tienen una propiedad llamada Tag (podemos asignarla y recuperarla mediante los métodos setTag() y getTag() respectivamente) que puede contener cualquier tipo de objeto, por lo que resulta ideal para guardar nuestro objeto ViewHolder. De esta forma, cuando el parámetro convertView llegue informado sabremos que también tendremos disponibles las referencias a sus controles hijos a través de la propiedad Tag. Veamos el código modificado de getView() para aprovechar esta nueva optimización:

public View getView(int position, View convertView, ViewGroup parent)
{
    View item = convertView;
    ViewHolder holder;

    if(item == null)
    {
        LayoutInflater inflater = context.getLayoutInflater();
        item = inflater.inflate(R.layout.listitem_titular, null);

        holder = new ViewHolder();
        holder.titulo = (TextView)item.findViewById(R.id.LblTitulo);
        holder.subtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);

        item.setTag(holder);
    }
    else
    {
        holder = (ViewHolder)item.getTag();
    }

    holder.titulo.setText(datos[position].getTitulo());
    holder.subtitulo.setText(datos[position].getSubtitulo());

    return(item);
}

Con estas dos optimizaciones hemos conseguido que la aplicación sea mucho más respetuosa con los recursos del dispositivo de nuestros usuarios, algo que sin duda nos agradecerán.

El control GridView de Android presenta al usuario un conjunto de opciones seleccionables distribuidas de forma tabular, o dicho de otra forma, divididas en filas y columnas. Dada la naturaleza del control ya podéis imaginar sus propiedades más importantes, que paso a enumerar a continuación:
  • android:numColumns, indica el número de columnas de la tabla o “auto_fit” si queremos que sea calculado por el propio sistema operativo a partir de las siguientes propiedades. 
  • android:columnWidth, indica el ancho de las columnas de la tabla. 
  • android:horizontalSpacing, indica el espacio horizontal entre celdas. 
  • android:verticalSpacing, indica el espacio vertical entre celdas. 
  • android:stretchMode, indica qué hacer con el espacio horizontal sobrante. Si se establece al valor “columnWidth” este espacio será absorbido a partes iguales por las columnas de la tabla. Si por el contrario se establece a “spacingWidth” será absorbido a partes iguales por los espacios entre celdas.
Veamos cómo definiríamos un GridView de ejemplo en nuestra aplicación:

<GridView android:id="@+id/GridOpciones"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:numColumns="auto_fit"
    android:columnWidth="80px"
    android:horizontalSpacing="5px"
    android:verticalSpacing="10px"
android:stretchMode="columnWidth" />

Una vez definida la interfaz de usuario, la forma de asignar los datos desde el código de la aplicación es completamente análoga a la ya comentada tanto para las listas desplegables como para las listas estáticas: creamos un array genérico que contenga nuestros datos de prueba, declaramos un adaptador de tipo ArrayAdapter pasándole en este caso un layout genérico (simple_list_item_1, compuesto por un simple TextView) y asociamos el adaptador al control GridView mediante su método setAdapter():

private String[] datos = new String[25];
//...
for(int i=1; i<=25; i++)
    datos[i-1] = "Dato " + i;

ArrayAdapter<String> adaptador =
    new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datos);

final GridView grdOpciones = (GridView)findViewById(R.id.GridOpciones);

grdOpciones.setAdapter(adaptador);

Por defecto, los datos del array se añadirán al control GridView ordenados por filas, y por supuesto, si no caben todos en la pantalla se podrá hacer scroll sobre la tabla. Vemos en una imagen cómo queda nuestra aplicación de prueba:

En cuanto a los eventos disponibles, el más interesante vuelve a ser el lanzado al seleccionarse una celda determinada de la tabla: onItemSelected. Este evento podemos capturarlo de la misma forma que hacíamos con los controles Spinner y ListView. Veamos un ejemplo de cómo hacerlo:

grdOpciones.setOnItemSelectedListener(
    new AdapterView.OnItemSelectedListener() {
        public void onItemSelected(AdapterView<?> parent,
        android.view.View v, int position, long id) {
            lblMensaje.setText("Seleccionado: " + datos[position]);
        }
    public void onNothingSelected(AdapterView<?> parent) {
        lblMensaje.setText("");
    }
});
sgoliver.net