2012-03-15 10 views
9

bastante nuevo desarrollador de Android aquí.getView llamado con la posición incorrecta al desplazarse rápidamente

Me he encontrado con un problema extraño que no estoy seguro de cómo solucionarlo. He leído muchos problemas por aquí que parecen ser el mismo problema que tengo, pero las soluciones a sus problemas nunca parecían aplicables a lo que está sucediendo aquí.

Tengo una vista de lista personalizada configurada usando una clase separada que amplía BaseAdapter y usa un titular de vista (configuré esto mirando varios ejemplos diferentes de listas de visitas personalizadas). Tiene una vista de texto, un botón, una barra de progreso y un botón de imagen, y estos elementos están ocultos/cambiados según una AsyncTask utilizada para descargar un archivo.

Todo funciona bien cuando me desplazo lentamente por la lista. Cuando desplazo la lista rápidamente arriba/abajo, es como si las vistas de algunas celdas de la lista se reasignaran a otras celdas.

Puedo poner esta línea en la parte superior de mi método "getView" en mi clase de adaptador:

@Override 
    public View getView(final int pos, View convertView, ViewGroup parent) 
    { 
     Log.i("ks3","getView called, position is " + pos); 

tengo 7 artículos en mi lista, y 6 de ellos caben en la pantalla. Cuando por primera vez se muestra, veo esto en mi registro:

getView called, position is 0 
getView called, position is 1 
getView called, position is 2 
getView called, position is 3 
getView called, position is 4 
getView called, position is 5 

cuando me desplazo hacia abajo lentamente hasta el siguiente elemento, este se imprime siguiente:

getView called, position is 6 

cuando me desplazo hacia arriba, esta :

getView called, position is 0 

Bajar y subir lentamente producen estos resultados sin problemas.

Cuando inicio el desplazamiento de ida y vuelta rápida, imprime este mensaje un montón de veces, sobre todo mostrando 6 y 0 como la posición, pero de vez en cuando los verán así:

getView called, position is 1 
getView called, position is 5 

Las posiciones 1 y 5 no deberían ser llamados, ya que siempre están en pantalla. Es como si getView tuviera todo revuelto. Al mismo tiempo, como dije antes, mi lista se verá extraña. Los botones de imagen se moverán hacia arriba o hacia abajo en una celda donde no deberían estar.

Si vuelvo a desplazarme suavemente, solo veo 0 y 6 para la posición nuevamente.

Honestamente, no estoy seguro de cómo solucionar esto. Pensé que tal vez podría limitar la velocidad con la que puedes desplazarte por la lista, pero no he podido encontrar nada que funcione.

Gracias!

Editar: Quería actualizar un par de cosas con respecto a esta pregunta. La primera es que, a partir de un comentario aquí y del video de Google en listView, me ha llamado la atención que se pueda llamar a getView para otras cosas, luego de lo que imaginé que era, como mediciones, y por eso no debería alarmarme. por lo que originalmente pensé que era parte de mi problema (que era que creía que se llamaba a getView con las posiciones incorrectas).

En segundo lugar, vi varias veces que es una muy mala idea "almacenar en caché las vistas dentro de su adaptador". No estoy exactamente claro en lo que esto significa, pero estoy bastante seguro de que es una de las cosas que estoy haciendo mal (guardo una instancia de un botón, progressBar, imageButton ...).

Eso, junto con el hecho de que actualizo las vistas fuera de getView y no utilizo notifyDataSetChanged(); los que están juntos probablemente estén causando algunas cosas inseguras.

+0

A menos que su ListView personalizado esté haciendo algo funky, esto suena como un problema de reciclaje, publique su código getView() completo. – dmon

+0

Si no encuentra ningún error en su código y utiliza Honeycomb o superior, puede intentar deshabilitar la aceleración de hardware para el diseño de su lista. La aceleración de hardware puede causar tales errores. – zapl

+1

Parece un problema de reciclaje. Voy a echar un vistazo al video de Google en listViews y ver si hay algún problema evidente con mi uso de listView. – Droidmon

Respuesta

13

Tiene la razón que ListView está reutilizando vistas en diferentes lugares de la pantalla. Es una optimización para mantener el uso de la memoria razonable y rápido al no asignar nuevas vistas todo el tiempo.

Lo más probable es que esté utilizando LiewView incorrectamente. Mirar this talk on how to properly use ListView para obtener la historia completa, pero aquí está lo más destacado:

  1. "Posición" se refiere a la ubicación de sus datos en la lista de adaptadores. Si los datos de su adaptador están en una matriz, este sería el índice en esa matriz.
  2. "ID" se refiere al valor de los datos en sí. Si tiene una lista de nombres y los utiliza, su posición cambiará, pero su ID no lo hará.
  3. "Índice" hace referencia a la ubicación relativa de una vista con respecto al área de pantalla visible. Probablemente nunca necesites esto.
  4. No manipule las vistas (o intente almacenarlas en caché) fuera del método getView() de su adaptador o obtendrá un comportamiento extraño.
  5. Si la llamada al getView(int, View, ViewGroup) proporciona una instancia de vista, llene sus campos en lugar de inflar vistas totalmente nuevas. Suponiendo que ha implementado correctamente getItemType(), siempre obtendrá el tipo correcto de View para volver a llenar.
  6. Realice su método getView() tan rápido como sea posible, y solo realice levantamientos pesados ​​en otros hilos.
  7. El hecho de que el contenedor llamado getView() no significa necesariamente que se mostrarán los datos. El marco usa estos para fines de medición. Como el trabajo podría desecharse, esta es otra razón para asegurarse de que getView() sea lo más rápido posible.
  8. Cuando algo le sucede a sus datos que necesita mostrar en pantalla, digamos que su descarga está completa, es cuando llama al notifyDataSetChanged(). No toque las vistas directamente, se llenarán en el siguiente bucle UI cuando se vuelva a dibujar.

Acabo de pasar unos días reelaborando un ListView que se implementó ingenuamente, siento su dolor. ¡Los resultados han valido la pena, sin embargo!

+2

Argyle, muchas gracias por el enlace. No sé lo suficiente sobre cómo funciona ListView, y admitiré que estoy actualizando las vistas fuera de getView, así que tendré que encontrar la manera de trabajar con notifyDataSetChanged(). – Droidmon

+0

¡Sir pls siente mi dolor! ¿Puede usted responder a esta pregunta? Http://stackoverflow.com/questions/22451081/how-to-track-the-position-of-item-while-scrolling-in-a-long-listview –

+0

Tengo algunos medios pesados procesando y agregando forma dinámica en cada artículo y simplemente encuentro el lugar para eso en getview(). por lo que respecta a su punto cuando puedo moverlo? – Kenji

0

getView se llama para cada elemento de la lista que debe dibujarse pero que anteriormente no estaba visible en la pantalla. Si se desplaza rápidamente puede obtener posiciones extrañas, pero eso no debería causar errores como usted describe (supongo que Android no tiene ningún error aquí ya que esas ListViews están bastante bien probadas). P.ej. Cuando te desplazas lo suficientemente rápido, puedes tener 2 vistas que deben dibujarse. Quizás haya algo más incorrecto en su código.

+0

Ah, vale, eso es bueno saber, me alegro de que esto no sea, o al menos no debería ser lo que está causando el problema. – Droidmon

4

siempre use este enfoque en getview: convertView = inflater.inflate (R.layout.listinflate, parent, false);

Y deje que su actividad implemente onScrollListener y, cuando el usuario se arroje, notifique a su adaptador de vista de lista que nunca se tomará la molestia de actualizar los elementos correctamente. y cuando el usuario ya no esté arrojando, dígale al adaptador que se encargue de proporcionar datos en getView.

Eso resolvió todos mis problemas. Y una cosa más no use wrap_content en la altura de la vista de lista.set smoothscroll false también.

+0

no usar wrap_content me ayudó – pkacprzak

1

tuve el mismo problema: en el adaptador estaba cambiando el color de fondo en el método getView pero a veces la vista de lista era "reciclando" las vistas (obteniendo el fondo incorrecto). He resuelto simplemente poniendo "más" a cada

if(...){changeBackground}

añadí

else {restore default background}

Y entonces funcionó sin problemas

+0

¡Hombre, eres increíble! Dediqué una buena parte de mi mañana a resolver este problema. ¡Muchas gracias! –

+0

no hay problema! Me alegra oír eso! ;) – Mark

0

Para el registro y extender la respuesta de @ Marcos

Android realiza la siguiente tarea. Digamos que tengo una lista con 100 elementos. Cuando esté cargando, se llama getview para el elemento 0,1,2,3 ... a 10, por ejemplo. Y si continuamos, la siguiente posición será la 11. Sin embargo, la posición 11 recicla la misma vista que la posición 0. Entonces, la vista para la posición 11 existe pero tiene el valor incorrecto (el valor 0).

Respuesta: Modifique los datos si la vista es nula o no. Si la vista es nula, desinfle y modifique los valores de la vista. De lo contrario, modifique los valores de la vista.

que permite decir que tengo una item_view (layout) con una sola TextView, el adaptador debe ser de la siguiente manera:

Versión derecha:

public class XXXAdapter extends ArrayAdapter<XXX> {.... 
@NonNull 
@Override 
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 
    if (convertView==null) { 
     LayoutInflater inflater = LayoutInflater.from(this.getContext()); 
     convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false); 
    } 
    ***Object** item=this.getItem(position); 
    TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**); 
    txt.setText(String.valueOf(position)); 
    return convertView; 
    // return super.getView(position, convertView, parent); 
} 

versión incorrecta:

public class XXXAdapter extends ArrayAdapter<XXX> {.... 
@NonNull 
@Override 
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 
    if (convertView==null) { 
     LayoutInflater inflater = LayoutInflater.from(this.getContext()); 
     convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false); 
     ***Object** item=this.getItem(position); 
     TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**); 
     txt.setText(String.valueOf(position)); 
     return convertView; 
     // return super.getView(position, convertView, parent); 
    } 
} 
Cuestiones relacionadas