Hace una semana comentábamos el modo de crear un ListView para Android con, como mínimo, un CheckBox en cada fila de la lista. Este mini tutorial de hoy, en el que vamos a explicar como marcar de una todos los CheckBox de un ListView, vamos a basarnos en el contenido explicado en dicho artículo.
En principio la metodología parece sencilla, en nuestro fichero main.xml, además de declarar un ListView, declaramos en la parte inferior un CheckBox cuyo comportamiento determinaremos más adelante, de manera que el contenido del main.xml quedaría de la siguiente manera:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
<CheckBox
android:id="@+id/chkSelectAll"
android:text="@string/chkSelectAll"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Hasta aquí todo ha sido coser y cantar, de nuevo hemos de definir un fichero con el Layout de cada una de las filas de lo que será nuestro ListView, en nuestro caso item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<CheckBox
android:id="@+id/chkItem"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Una vez que tenemos hecho esto nos bastará con definir el comportamiento de la clase principal para que al marchar el chkSelectAll se marquen todos los CheckBox de la lista, para ello en un principio, con poner el array que determina el valor de cada CheckBox a true o false, en función del caso, debería sernos suficiente. Para ello añadiríamos en el onCreate las liguientes lineas:
chkAll = (CheckBox)findViewById(R.id.chkSelectAll);
chkAll.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
for (int i = 0; i < adapter.getItemsLength(); i++) {
adapter.set(i, b);
}
}
});
El problema nos lo encontramos cuando lo que queremos es que además de marcarlos en el sistema queremos que se visualicen los CheckBox marcados directamente, para ello, lo que podemos hacer (AVISO: es una solución un poco cutre y simplemente para salir del paso) es reasignar el adaptador, para lo que después del for podríamos añadir las siguientes lineas:
int pos = list.getFirstVisiblePosition();
list.setAdapter(adapter);
list.setSelection(pos);
Si ahora compilamos y ejecutamos el código podremos observar como el comportamiento es el deseado.
Android es un gran sistema operativo para móviles que le supone una competencia directa al iOS, y por lo tanto meterse como programador en su mundo puede ser algo muy atractivo. Pero como programadores más o menos experimentados sabemos que no todo es un paseo, tal y como algunos pretenden plantearlo; siempre surgen problemas que habremos de solucionar. Hoy, vamos a ver como evitar que, al tener un ListView en el que cada fila contenga un CheckBox, al hacer scroll, los elementos de la parte no visible se marquen al marcar uno de la parte visible de manera automática.
¿Por qué?
Esto sucede porque Android reutiliza las mismas vistas una y otra vez a la hora de pintar los elementos en pantalla, de manera que aunque el CheckBox que ha de mostrar no está seleccionado, se mostrará como tal dado que uno visible anteriormente ya lo estaba. De este modo se ahorra una gran cantidad de ciclos de CPU a la hora de generar la imagen que se va a mostrar.
Cómo podemos ver, el comportamiento por defecto del sistema operativo no siempre nos viene bien ya que, en casos como este, puede dar lugar a confusiones en el usuario final de la aplicación.
Solución
La manera de solucionar este pequeño inconveniente, aunque no de la forma más óptima, es tan simple como establecer a mano el valor del CheckBox que ha de mostrarse en cada momento, para ello tendremos que apoyarnos en una serie de clases y ficheros XML.
Antes de nada, vamos a analizar los ficheros que componen nuestro Layout, es decir, el que establece la distribución de la pantalla principal y el que establece el aspecto de cada una de las filas.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
</LinearLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<CheckBox
android:id="@+id/chkItem"
android:visibility="visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Tal y como se puede observar, hasta este momento no hemos hecho más que definir dos layouts, uno que contiene un ListView y otro que contiene el CheckBox que ha de aparecer en cada fila.
Prosigamos programando nuestra clase Java para manejar estos Layouts:
package org.dipler.chkList;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.CompoundButton.OnCheckedChangeListener;
public class Main extends Activity {
private ChkListAdapter adapter;
private ListView list;
private CheckBox chkAll;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list = (ListView)findViewById(R.id.list);
fillList();
}
private void fillList() {
int num = 50;
adapter = new ChkListAdapter(num);
for(int i = 0; i < num; i++){
adapter.addItem(new Item(String.valueOf(i), "item number " + i));
}
list.setAdapter(adapter);
}
private class ChkListAdapter extends BaseAdapter {
private ArrayList<Item> items = new ArrayList<Item>();
private LayoutInflater inflater;
private boolean[] itemSelection;
public ChkListAdapter(int size) {
inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.itemSelection = new boolean[size];
}
public void addItem(final Item item) {
items.add(item);
notifyDataSetChanged();
}
@Override
public int getCount() {
return items.size();
}
@Override
public String getItem(int position) {
return items.get(position).toString();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
convertView = inflater.inflate(R.layout.row, null);
final ViewHolder holder = new ViewHolder();
holder.chkItem = (CheckBox)convertView.findViewById(R.id.chkItem);
holder.chkItem.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
itemSelection[position] = holder.chkItem.isChecked();
}
});
holder.chkItem.setChecked(itemSelection[position]);
convertView.setTag(holder);
holder.chkItem.setText(getItem(position));
return convertView;
}
public int getItemsLength() {
if(itemSelection == null) return 0;
return itemSelection.length;
}
public void set(int i, boolean b) {
itemSelection[i] = b;
}
}
public static class ViewHolder {
public CheckBox chkItem;
}
}
Como podemos observar el la, bueno, las clases Java que hemos declarado tenemos:
- La clase Activity principal que utiliza el Layout main.xml y que en el método onCreate() rellena el ListView con elementos, en nuestro caso de tipo Item (Item es una clase cualquiera con dos atributos, un id y un texto)
- El adapter, que es la clase que se va a dedicar a repintar las filas tantas veces como sea necesario llamando al método getView() en cada una de esas ocasiones.
- La clase ViewHolder que nos facilita el trabajo con la clase Row, esta clase no es en sí necesaria, pero vi la idea en amberfog y me gustó, de hecho de esta misma página obtuve una visión mucho más clara del funcionamiento de estos artificios llamados “Views”
El procedimiento para evitar que se marquen solos los CheckBox que cumplen el periodo, es sencillo, llevamos un array de booleanos en el que establecemos el estado de cada elemento de la lista confome cambia el valor del CheckBox, de manera que cada vez quedeseemos mostrar dicho elemento, el método getView(), en lugar de pintar como cree que debería pintar estos CheckBox, los muestra marcados o desmarcados en función del array que hemos creado y que vamos actualizando dinámicamente.
El método propuesto no es el más eficiente que nos podamos encontrar, pero funciona y nos puede ayudar a salir del paso.

Como ya sabréis, las versiones de Diary 0.2.x están girando en torno a los diferentes tipos de entradas que podemos tener, así como en una mejora del rendimiento y de la seguridad que presenta la aplicación para permitir al usuario utilizar de una forma sencilla y práctica la aplicación sin tener que preocuparse de nada en absoluto. Dentro de las mejoras podemos diferenciar las de dos tipos:
- De usuario (mejora la experiencia del usuario para con la aplicación)
- Nueva interfaz de usuario
- Nueva interfaz de búsqueda con nuevos filtros
- Nuevo estilo de marcado de tareas pendientes y terminadas (imágenes en lugar de chekbox)
- De servicio (mejora la calidad del servicio de forma transparente al usuario)
- Ajax combinado con XML
- Mayor seguridad a la hora de intentar replicar sesiones
Por el momento la aplicación está optimizada, como ya hemos dicho, para navegadores modernos, es decir, últimas versiones de Chrome y Firefox, en este último caso, y para sacarle el máximo jugo a la interfaz, será necesario el uso de Firefox 4.0
Muchos son los manuales que a día de hoy nos “enseñan”, o al menos eso dicen, a publicar con todo detalle los pasos para publicar nuestras aplicaciones para android en el market, y no digo que no sea cierta dicha aseveración, sino que, además deberían contar algunos inconvenientes a la hora de hacerlo, vamos a intentar proponer estos baches que a mi me han hecho perder un poco de tiempo para que no tengáis que perderlo vosotros también.
Este artículo parte de que todo lo que pone en el resto de páginas lo sabéis y no tenéis demasiado problema a la hora de hacer (en caso de dudas, preguntad sin temor en los comentarios de este artículo).
Lo más probable, es que si estás leyendo este artículo es que desarrolles aplicaciones para dispositivos con una versión de Android instalada superior a la 1.5.
A la hora de publicar para estas versiones aplicaciones hemos de tener en cuenta varios parámetros de fichero AndroidManifest.xml y, es que, para que se publique en el android para todos los dispositivos tendremos que decirlo expresamente en este fichero:
<manifest ...>
<uses-sdk android:minSdkVersion="4" />
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true" />
En estas líneas estamos diciendo que independientemente del tipo de pantalla de nuestro dispositivo, la aplicación va a ser compatible. Si no incluimos estas líneas, veremos como nuestra aplicación aparece en el Market para pantallas normales y grandes, pero no para pequeñas, es decir, en mi caso, la aplicación no se mostraba para mi HTC Wildfire, dispositivo en el que había estado haciendo todo el proceso de depurado de la aplicación.
Por otra parte, si deseamos publicar una aplicación de pago, deberemos tener una empresa registrada para darla como dato en la página de Google Checkout, la cual nos serviría para recibir pagos. Como alternativa recomiendo hacer uso de sistemas publicitarios en caso de que deseemos sacar algo de dinero de la aplicación y no tengamos empresa a la que asociar nuestra cuenta.