2011-07-08 12 views
6

Tengo un comportamiento ListView muy extraño cuando uso un StateListDrawable como fondo. Intenté seguir la respuesta a la publicación this, ya que no estaba obteniendo el estado state_checked para trabajar, pero ahora mi ListView se está volviendo loco.Comportamiento extraño del elemento ListView y del fondo del selector de estado dibujables

Cuando hago clic en un elemento, no cambia inmediatamente de color al elemento state_checked en el selector. Sin embargo, después de hacer clic un poco, muchas de las vistas cambiarán repentinamente al fondo state_checked. Es aparentemente aleatorio.

Aquí es mi estado selector de código xml:

<?xml version="1.0" encoding="utf-8"?> 
<selector 
    xmlns:android="http://schemas.android.com/apk/res/android"> 

    <item android:state_pressed="true" > 
     <shape> 
      <gradient 
       android:startColor="@color/grey" 
       android:endColor="@color/darkgrey" 
       android:angle="270" /> 
      <stroke 
       android:width="0dp" 
       android:color="@color/grey05" /> 
      <corners 
       android:radius="0dp" /> 
      <padding 
       android:left="10sp" 
       android:top="10sp" 
       android:right="10sp" 
       android:bottom="10sp" /> 
     </shape> 
    </item> 

    <item android:state_focused="true" > 
     <shape> 
      <gradient 
       android:endColor="@color/orange4" 
       android:startColor="@color/orange5" 
       android:angle="270" /> 
      <stroke 
       android:width="0dp" 
       android:color="@color/grey05" /> 
      <corners 
       android:radius="0dp" /> 
      <padding 
       android:left="10sp" 
       android:top="10sp" 
       android:right="10sp" 
       android:bottom="10sp" /> 
     </shape> 
    </item> 

    <item android:state_checked="true"> 
     <shape> 
      <gradient 
       android:endColor="@color/brown2" 
       android:startColor="@color/brown1" 
       android:angle="270" /> 
      <stroke 
       android:width="0dp" 
       android:color="@color/grey05" /> 
      <corners 
       android:radius="0dp" /> 
      <padding 
       android:left="10sp" 
       android:top="10sp" 
       android:right="10sp" 
       android:bottom="10sp" /> 
     </shape> 
    </item> 

    <item android:state_selected="true"> 
     <shape> 
      <gradient 
       android:endColor="@color/brown2" 
       android:startColor="@color/brown1" 
       android:angle="270" /> 
      <stroke 
       android:width="0dp" 
       android:color="@color/grey05" /> 
      <corners 
       android:radius="0dp" /> 
      <padding 
       android:left="10sp" 
       android:top="10sp" 
       android:right="10sp" 
       android:bottom="10sp" /> 
     </shape> 
    </item> 

    <item>   
     <shape> 
      <gradient 
       android:startColor="@color/white" 
       android:endColor="@color/white2" 
       android:angle="270" /> 
      <stroke 
       android:width="0dp" 
       android:color="@color/grey05" /> 
      <corners 
       android:radius="0dp" /> 
      <padding 
       android:left="10sp" 
       android:top="10sp" 
       android:right="10sp" 
       android:bottom="10sp" /> 
     </shape> 
    </item> 

</selector> 

Y aquí está mi clase .java para mi punto de vista personalizada implementando comprobable:

public class Entry extends LinearLayout implements Checkable { 

    public Entry(Context context) { 
     super(context, null); 

     // Inflate this view 
     LayoutInflater temp = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     temp.inflate(R.layout.entry, this, true); 

     initViews(); 
    } 

    private static final int[] CheckedStateSet = { 
     android.R.attr.state_checked 
    }; 

    private void initViews() { 
     this.setBackgroundResource(R.drawable.listview_row); 
    } 

    public boolean isChecked() { 
     return _checked; 
    } 

    public void toggle() { 
     _checked = !_checked; 
    } 

    public void setChecked(boolean checked) { 
     _checked = checked; 
    } 

    @Override 
    protected int[] onCreateDrawableState(int extraSpace) { 
     final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 
     if (isChecked()) { 
      mergeDrawableStates(drawableState, CheckedStateSet); 
     } 
     return drawableState; 
    } 

    @Override 
    public boolean performClick() { 
     toggle(); 
     return super.performClick(); 
    } 
} 

He hurgó durante unas horas tratando para resolverlo, pero lamentablemente debe ceder a pedir ayuda. ¿Alguien puede ver algo mal con el código anterior que haría que el ListView se comporte de manera extraña en los artículos? Puedo publicar más código también, si es necesario.

+0

Creo que debería usar View Wrapper clase – Nitin

Respuesta

21

Cuando se trabaja con ListView es muy importante tener siempre en cuenta que las vistas son la presentación y el adaptador es el modelo de datos .

Esto significa que todo su estado debe estar en el adaptador (el modelo de datos), no en las vistas.

De lo que puedo decir de su código, que tienen una visión que está mostrando un estado de verificación, y que estado se encuentra en la vista no en el adaptador. Es decir, cuando el usuario hace clic en ese elemento en la lista, la vista que se utiliza para mostrar su elemento tiene su estado de verificación interna cambiado para alternar lo que se muestra al usuario.

Pero como la vista no es el modelo de datos, este estado con el que está jugando aquí es transitorio y no está realmente asociado con el elemento del adaptador al que se hace clic.

El problema más obvio que esto provoca viene con el reciclaje de vistas. Cuando se desplaza por un ListView, cuando los elementos se desplazan hacia el final y aparecen nuevos en la parte inferior, las vistas utilizadas para mostrar los elementos antiguos se vuelven a usar para mostrar los nuevos. Esto es mucho más eficiente que tener que inflar una nueva jerarquía de vista de elemento cada vez que se muestra un nuevo elemento.

Como tiene su estado en la vista, cuando ocurre este reciclaje, ese estado en la vista se vuelve a asociar al azar con algún elemento nuevo. Esto puede suceder en muchos casos, no solo desplazándose.

La solución es poner su estado de comprobación en el adaptador e implementar Adapter.getView() para establecer el estado verificado de la vista según el estado que tenga ahora en el adaptador.De esta forma, cada vez que se recicla una vista (y se llama al getView() para vincularle la nueva fila de datos), actualizará su estado verificado para seguir correctamente los datos nuevos que se muestran.

+0

ohhhh sí, ya veo. Entonces, en este momento, mi adaptador recupera sus datos de una base de datos (que no tiene una columna de "clic", ni creo que lo haga). ¿Es una buena práctica mantener el estado de los elementos actualmente controlados dentro de algún tipo de colección en el adaptador? Además, cuando uso el método 'ListView.setItemChecked()', ¿eso también comprueba el elemento en el adaptador? –

+0

Considere utilizar el estado de comprobación de ListView si hace lo que desea, puede hacer una selección única o múltiple. Se ocupa de lo que debe hacer: realizar un seguimiento del estado asociado a cada identificador de fila (NO índice de fila, porque puede cambiar si los datos de la base de datos cambian). Necesitará que su UI interactúe correctamente con la vista de lista para mostrar este estado al implementar Comprobable en la vista de nivel superior en el elemento de la lista. Hay muestras de esto en ApiDemos. – hackbod

+0

Gracias hackbod, pero creo que esto realmente me confundió aún más, principalmente porque eso es exactamente lo que estoy haciendo. Mi vista de elementos de lista de nivel superior de ListView es la clase de entrada seleccionable que publiqué para el código anterior. He evitado anular el método performclick() en el pasado, y solía confiar en el método ListView.setItemChecked() en mi instancia de listview. Sin embargo, esto no funcionó y las vistas no cambiaron su fondo al de mi elemento state_checked (aunque los demás estados se muestran correctamente). ¿Es esto un problema separado, o está relacionado? –

3

No creo que el problema provenga del código anterior. He tenido este problema antes y tenía que ver con el reciclaje de las vistas en la lista. Este puede ser el caso para usted si su lista continúa fuera de la pantalla. Si este es el caso, una buena manera de solucionarlo es almacenar los estados de los elementos en una lista para que pueda hacer un seguimiento de ellos y basar sus estados en la lista que creó. Eche un vistazo a this y this para obtener más información sobre el reciclaje de las vistas.

0

Al hacer algo similar con un conjunto bastante complejo de ListViews, agregué una lista extra al adaptador y agregué la posición de los elementos que se le hicieron clic. getView (...) luego infla/recicla la vista y justo antes de que finalice comprueba el estado del elemento y el estado del adaptador interno para decidir qué fondo aplicar.

También configuré el archivo xml de la lista de estados para hacer que el fondo sea transparente cuando está presionado para que el selector sea visible, funciona de maravilla.

Cuestiones relacionadas