0 votos

Buenas tardes… por aquí estoy otra vez. Os pego el código del Adapter para llenar un listview y luego os comento:

public class Adaptador extends BaseAdapter { // implements OnClickListener {

static class ViewHolder {
ImageView imagenview;
TextView text_nombre;
TextView text_descripcion;
TextView text_indice;
TextView text_fecha;
ProgressBar progreso;
}

private Cursor datos;
private LayoutInflater inflater = null;

public Adaptador(Context contexto, Cursor valores) {
super();
this.datos = valores;
inflater = LayoutInflater.from(contexto);
datos.moveToLast();
datos.moveToFirst();
}

@Override
public int getCount() {
return datos.getCount();
}

@Override
public Object getItem(int posicion) {
return datos.moveToPosition(posicion);
}

@Override
public long getItemId(int posicion) {
return posicion;
}

@Override
public View getView(int posicion, View convertView, ViewGroup parent) {

final ViewHolder holder;

if (convertView == null) {

convertView = inflater.inflate(R.layout.itemlistalugares, parent,
false);

holder = new ViewHolder();

holder.imagenview = (ImageView) convertView
.findViewById(R.id.lst_foto);
holder.text_nombre = (TextView) convertView
.findViewById(R.id.lst_nombre);
holder.text_descripcion = (TextView) convertView
.findViewById(R.id.lst_descripcion);
holder.text_indice = (TextView) convertView
.findViewById(R.id.lst_indice);
holder.text_fecha = (TextView) convertView
.findViewById(R.id.lst_fecha);
holder.progreso = (ProgressBar) convertView
.findViewById(R.id.muestroProgreso);

convertView.setTag(holder);

} else {

holder = (ViewHolder) convertView.getTag();

}

getItem(posicion);

if (datos.getString(datos.getColumnIndex(“foto”)) != null) {
holder.progreso.setVisibility(View.VISIBLE);
transformoImagen(holder.imagenview, holder.progreso,
Uri.parse(datos.getString(datos.getColumnIndex(“foto”))));

}

holder.text_nombre.setText(datos.getString(datos
.getColumnIndex(“nombre”)));
holder.text_descripcion.setText(datos.getString(datos
.getColumnIndex(“descripcion”)));
holder.text_indice.setText(datos.getString(datos.getColumnIndex(“id”)));

holder.text_fecha
.setText(datos.getString(datos.getColumnIndex(“fecha”)));

// holder.text_nombre.setOnClickListener(this);
// holder.text_descripcion.setOnClickListener(this);
// holder.imagenview.setOnClickListener(this);

// convertView.setLongClickable(true);

return convertView;
}

private void transformoImagen(final ImageView laImagen,
final ProgressBar progreso, final Uri uri) {

//si no hago esto no consigo saber el tamaño del imageview para escalar la foto
//y no entiendo por qué, en otros sitios leo perfectametne el height y width sin problema en el onCreateView
ViewTreeObserver viewTreeObserver = laImagen.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() {

@Override
public boolean onPreDraw() {

laImagen.getViewTreeObserver().removeOnPreDrawListener(this);

int ancho = laImagen.getWidth();

//AsyncTask para convertir la foto en otro hilo y no parar el resto de la aplicación
//le paso el imageview, el progressbar y el ancho que quiero para la nueva foto, así como la uri de la foto a convertir
//como dato de entrada, lo otro va al constructor.
//al final devuelve un BitMap que cargo en el imageview que le paso
new Convierto(laImagen, progreso, ancho).execute(uri);
return true;
}
});

}
}

A ver… básicamente lo que hace es llenar un listview personalizado con una imagen, dos textview con info, otro textview oculto con un ínidce para enlazarlos con una base de datos, un progressbar para indicar que se está trabajando escalando la foto en otro hilo y otro textview con la fecha de asiento del registro mostrado.
 

La lista se carga bien, y funciona la relación con la base de datos, al clickar sobre cada item puedo identificar correctamente su info en la base de datos.

El problema que tiene, es que en el primer elemento de la lista que se visualiza en pantalla, mientras se carga la misma, veo como todas las fotos de los demás elementos de la lista se visualizan una detrás de otra en el imageview del primer item, y al final no necesariamente queda la foto que corresponde a ese elemento. Si clicko en él la info es correcta, incluso la de la foto aunque la que veo no es la que le corresponde. Cada vez que refresco la lsita, sucede lo mismo pero puede variar la foto que veo, no siempre es la misma.

He repasado el adapter una y otra vez y no acierto a descifrar qué está pasando.

A ver si me podéis echar un cable.

Muchas gracias por anticipado.

Un saludo

por en Android

1 Respuesta

0 votos
Mejor respuesta

Es un problema de sobrescritura del mismo id de la la misma ImageView del mismo holder, ya que está apuntando siempre al mismo elemento cuando se realiza la carga en segundo plano. Hay que tener un poco de cuidado con esto, pero lo más aconsejable es que cuando se descargue la imagen se redibuje la fila del listado (he incluso mantenerlas en un HashMap con SoftReference para que no nos tire la memoria, y realizar un cacheado de imágenes, aunque esto complica. Puedes mirar esto en http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html)

por

Hola… gracias por la respuesta.

Cuando llamo a cargar la imagen lo hago en un AsynkTask porque escalarla para adaptarla al ImageView y que no dé OutOfMemory. Sigo los pasos indicados por http://developer.android.com/training/displaying-bitmaps/load-bitmap.html (Loading Large Bitmaps Efficiently), pero lo envuelvo en un AsynkTask pq es un proceso muy lento cuando coge imágenes de la galería o tomamos fotos directamente.

El caso es que he visto por ahí que tenemos la clase ThumbnailUtils que permite extraer una miniatura de la foto indicada para el tamaño que le digamos (extractThumbnail(Bitmap source, int width, int height).

¿Podría usar esta clase pasándole la foto (3, 4… 8 mpíxeles) de la cámara o la macenada en la SD para obtener una miniatura que mostrar en mi listView y evitar el OutOfMemory? ¿Sería más rápido que el método que utilizo de Loading Large Bitmaps Efficiently)?

Gracias nuevamente… un saludo.

Haces muy bien al envolverlo en el AsynkTask, así no bloqueas al usuario ni al hilo princial.

No sabría decirte si es más o menos rápido pues no he hecho un estudio comparativo de tiempos para ambos métodos. Ante la duda sobre eficiencia, acertarás con mucha probabilidad si utilizas la clase especializada (además de ahorrarte código), por lo que sí que te recomendaría que utilizaras ThumbnailUtils.
Hola otra vez…

llevo todo este tiempo intentando optimizar el Adapter y no hay manera. Pasa muchas veces más de las debidas por getView () para cada item del listview. Y no sigue una regla muy clara. Si cargo un único item, pasa 3 – 5 veces (depende si es móvil o tablet)… hasta el punto de que si tengo 12 ítems (9 visibles en la lista) se dispara hasta las 88 – 124 veces (depende si es móvil o tablet).

No acierto con el fallo… paso un cursor y lo recorro para generar la lista. También me he dado cuenta que la foto cargada en la primera fila, que es correcta, también es la que muestra en la última si hago scroll hasta el final, siendo incorrecto en este caso.

No entiendo nada… UNa cosa que he hecho, siguiendo tu consejo (si lo e ntendí bien) es pasar el holder al AsyncTask junto con un contentvalue con los datos del registro en el que estoy, de forma que en el onPreexecute() cargo los textos en la fila y en doInBackGoround (al que le paso la uri de la foto) miro si está la imagen cacheada (1º en memoria y sino 2º en disco), si no lo está la escalo y la cacheo y la devuelvo para cargarla en el ImageView de la fila en el onPostExecute().

He mirado infinidad de tutoriales y todos hacen igual: ViewHolder -> Cacheo de imagen en background… y el resultado que obtengo es el que te comento.

¿Dónde está en mi adapter el problema para que repita tantas veces getView en cada fila? Además, también he visto que al hacer scroll varias veces o volver a la lista desde otro fragment esta cada vez se ralentiza más y más. De hehco puse un progressbar en el layout de la fila para ver si está trabajando y llega un momento en que se queda un bue nrato en todas las filas supuestamente intentando cargar una imagen que ya tengo previamente cacheada tanto en memoria como en disco. Es más, si paso a ver un mapa no me coloca las etiquetas (también en segundo plano) hasta que termina todos los Asyntask de la lista… que sigo sin conseguir matar esos hilos cuando no estoy viendo el listview.

Resumiendo… que no entiendo nada… y ya no sé por donde más mirar.

Un saludo

Cuando se trabaja ya con cierta complejidad de listados es normal que empiecen a dar este tipo de problemas en Android, los hemos pasado todos, hasta que colocas las cosas en sus sitio (dependerá en gran medida de la estructura de tu app y de lo que quieras mostrar en el listado). Lo mejor es dividir el problema.

Esto es, crea un nuevo programa en el que hacer solo el listado con los AsynkTask y las imágenes (sin mapas y otros que no tengan que ver con el listado, ni si quiera el detalle del listado hace falta). Si se te bloquea la aplicación, seguramente haya algo en primer plano que está consumiendo demasiada memoria, hay que identificar qué es o qué puede estar ocasionando el cuello de botella. Pon logs en el método getView(), estudia cuando entra al utilizar tu aplicación y en que momentos dibuja en pantalla para ver si se están dibujando bien las imágenes en su lugar correcto. También puedes probar a escalar las imágenes antes de si quiera abrir el listado y guardarlas (bloquearás un poco al usuario al principio, pero ganarás en rapidez de dibujado después).

Ánimo Nitram!!! estoy convencido de que lo conseguirás apuntarte este punto :D

Buenas… ooootra vez…

Solo una pregunta, es que ya no se me ocurre qué más probar… cuando asigno un Adapter a un listview (en este caso uno que extiende de BaseAdapter al que s e le pasa un Cursor resultado de un rawquery a una base de datos SQLlite), este primero llama al constructor donde se le pasa el contexto u el Cursor (en este caso) como fuente de datos.

¿Luego qué hace? ¿Cómo recorre el cursor para mostrar los datos en cada item de la lista? Si tengo diez datos que mostrar… ¿pasa 10 veces por getView()? Si cada item del listview se infla desde un layout que contiene un imageview y tres textview, ¿cuántas veces pasa por getView()? ¿El adapter recorre automáticametne el cursor o de alguna forma tengo que avanzar yo las filas del cursor?

Es que me está volviendo loco este tema… ahora mismo, con 12 registros en el cursor de los que simultáneamente se ven 8 y parte de otro en pantalla cada uno con un Imageview y tres textview, pasa por getView más de 60 veces.

Si quiero avanzar yo el cursor con moveToNExt(), da error pq excedemos el número de registro, si lo hago con moveToPosition, se erpiten los registros en el listview pq pasa 60 veces por el move… para los 8 que realmente se ven en pantalla.

Sinceramente, si me pudieras exlicar cómo hace el adapter para recorrer los datos que le paso a lo mejor encontraría dónde está el fallo… lo he probado absolutamente todo… por lo menos hasta donde soy capaz de llegar.

Un saludo… gracias y perdona mi ineptitud.

Estos listados de Android siempre tan sarcásticos :)

Bueno a ver por pasos. Para la base de datos has de crear una clase SQLiteOpenHelper que requiere el Context, no el Cursor. El Cursor se obtiene de llamar a query().

No hagas el moveToNext() dentro del getView(). El getView() se repite cada vez que se dibuja en pantalla una fila del listado, por lo que si lo haces dentro, cada vez que Android dibuje una fila se repetirá el while que recorre tu cursor. Creo que es lo que te está fallando. Te recomiendo lo siguiente, haz la llamada a la base de datos fuera del Adapter, guarda la consulta en un ArrayLis. Luego puedes o bien le pasas en el contructor del Adapter el ArrayList para ir consultando la fila requerida cada vez que se llama al getView(); o utilizas el ejemplo que hice, que saco las iteraciones del getView() fuera para no tener que tocar el adaptador, esto te veas más cómodo.

Aunque ya te he medio respondido te respondo a cada una de tus preguntas:
¿Cómo recorre el cursor para mostrar los datos en cada item de la lista?: el Adapter no recorre el cursor, solo muestra los datos que se le pasen por el getView()
Si tengo diez datos que mostrar… ¿pasa 10 veces por getView()?: Suponiendo que quepan en pantalla a la primera sí. El getView() dibuja las filas que quepan en pantalla, al hacer scroll las filas que dejen de verse se destruirán, y las nuevas visibles llamarán a getView()
Si cada item del listview se infla desde un layout que contiene un imageview y tres textview, ¿cuántas veces pasa por getView()?: Pasa una vez por fila (siguiendo las instrucciones de la anterior pregunta), cada vez que pase por el getView() se dibujarán los imageview y los textview para cada fila
¿El adapter recorre automáticametne el cursor o de alguna forma tengo que avanzar yo las filas del cursor? El adapter solo muestra. Puedes medio hacer esto con Loader, aunque te tocará a ti lanzar las consultas.

Si te animas también puedes utilizar Loader (mira un ejemplo en http://developer.android.com/reference/android/app/LoaderManager.html), que se encarga de consultar en la base de datos cuando carga el listado. No es difícil de utilizar, aunque sí que hay que colocar las cosas en su sitio y tiene unas cuantas. El ejemplo que te pasé antes viene muy bien.

Siempre lo digo, con los listados de Android es como verdaderamente se aprende Android!!!

Buenas!

Tengo este mismo problema en mi listview. Yo obtengo los datos de un JSON y luego tengo un adaptador personalizado que extiende de BaseAdapter. Ya probé a cambiar a ArrayAdapter y nada. El caso es que tendría que visualizar una imagen y un texto y la imagen me daba los mismos problemas que comentabas aquí. He quitado esa imagen por si fuera ese el problema y aún así veo que entra en el getview muchas más veces de las que debería. Si meto 2 objetos recorre el getview unas 6 veces...

La verdad es que ya no sé que más probar y quería saber si habías podido solucionar este problema.

Gracias de antemano.

Un saludo.
Hola David,

Lo que pasa es que no se garantiza la cantidad de veces que se va a llamar, pudiendo ocurrir que Android llame varias veces al getView() que los elementos que hay (o menos veces si no caben en pantalla todos los elementos). Esto lo mejor es controlarlo desde el propio Adapter dependiendo de lo que necesites.
Hola a todos, a mi me sucede lo mismo, y según leí aquí, ustedes siguen sin solucionarlo, probé algunas cosas pero no consigo solucionar el problema, que otras opciones hay?

Hasta probé con cambiar mi ListView por un GridView y aun así persiste el problema.

Ya no se que más hacer, necesito arreglar eso, y si no me equivoco eso tiene que tener una solución, ya que hay varias aplicaciones que usan listview de la misma manera y todo sale como debe ser.

Aclaro algo, mi aplicación no recoge la info de una base de datos, sino que se alimenta a través de un rss, el cual almaceno en un arraylist.

Gracias de antemano.

Saludos!
Está puesto. Hay que tener una buena gestión de Views en Listados: Viewholder, cacheado, carga dinámica, etc
Gracias por responder, pero soy nuevo en el ambiente de programación, creo que sería mejor que me lo expliquen con peras y manzanas, jajaja

Ok, tengo entendido que carga las imagenes en el mismo imageview, y que alli radica el problema, o seaique tengo que utilizar viewholder, cacheado, carga dinamica?

Disculpa mi ignorancia

Gracias por contestar
Muchas gracias, probaré a ver que tal me va, luego aviso si me funciono
Mi aplicación ya implementaba estas soluciones que me mencionaste, solo note que en vez de Viewholder tengo un Placeholder, crees que eso afecta?