2011-07-08 11 views
6

Mam bardzo dziwne zachowanie ListView podczas używania StateListDrawable jako tło. Próbowałem postępować zgodnie z odpowiedzią na this post, ponieważ nie działał stan state_checked, ale teraz mój ListView szaleje.Dziwne zachowanie elementu ListView i tła selektora stanu do rysowania

Po kliknięciu elementu nie zmienia on natychmiast koloru elementu sprawdzanego przez stan w selektorze. Jednak po krótkim kliknięciu wiele widoków nagle przejdzie na tło sprawdzane przez state_check. Jest pozornie przypadkowa.

Oto mój stan kodu selektor 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> 

I tu jest moja klasa .java dla mojego widoku niestandardowego wdrażania Rejestrowalne:

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(); 
    } 
} 

jakie wysunął się za kilka godzin próbując aby to zrozumieć, ale niestety musi przyznać się do prośby o pomoc. Czy ktoś może zobaczyć coś nie tak z powyższym kodem, który mógłby spowodować dziwne zachowanie się ListView na przedmiotach? W razie potrzeby mogę też dodać więcej kodu.

+0

Myślę, że powinieneś użyć View Wrapper class – Nitin

Odpowiedz

21

Podczas pracy z ListView bardzo ważne jest, aby zawsze pamiętać, że widoki są prezentacjaa adapter jest model dane.

Oznacza to, że cały stan powinien znajdować się w adapterze (modelu danych), , a nie w widokach.

Z tego co mogę powiedzieć o kodzie, masz pogląd, że jest pokazujący stan wyboru, i że państwo jest w widzenia nie w adapterze. Oznacza to, że gdy użytkownik kliknie na ten element na liście, widok używany do wyświetlenia jego elementu ma swój wewnętrzny sprawdzony stan, aby przełączyć to, co jest pokazywane użytkownikowi.

Ale ponieważ widok nie jest modelem danych, ten stan, w którym grasz tutaj, jest przejściowy i nie jest faktycznie związany z klikaniem elementu adaptera.

Najbardziej oczywistym problemem związanym z tym problemem jest recykling widoków. Podczas przewijania za pomocą funkcji przewijania poza końcem i wyświetlania nowych u dołu widoki używane do wyświetlania starych elementów są ponownie używane do wyświetlania nowych elementów. Jest to o wiele bardziej wydajne niż konieczność nadmuchiwania nowej hierarchii widoków elementów za każdym razem, gdy pokazywany jest nowy element.

Ponieważ masz swój stan w widoku, kiedy następuje recykling, ten stan w widoku zostaje losowo powiązany z nowym elementem Może się to zdarzyć w wielu przypadkach, a nie tylko przewijaniu.

Rozwiązaniem jest umieszczenie stanu sprawdzenia w adapterze i zaimplementowanie Adapter.getView(), aby ustawić sprawdzony stan widoku na podstawie stanu istniejącego w adapterze.W ten sposób, gdy widok jest ponownie przetwarzany (i wywoływany w celu powiązania nowego wiersza danych), aktualizowany jest jego stan sprawdzany, aby poprawnie wyświetlał nowe dane, które wyświetla.

+0

ohhhh tak, teraz widzę. W tej chwili mój adapter pobiera dane z bazy danych (która nie ma "klikniętej" kolumny, jak również nie powinna). Czy najlepszą praktyką jest utrzymywanie stanu aktualnie zaznaczonych elementów wewnątrz jakiejś kolekcji w adapterze? Ponadto, gdy używam metody 'ListView.setItemChecked()', czy to również sprawdza element w adapterze? –

+0

Należy rozważyć użycie stanu sprawdzania ListView, jeśli robi to, co chcesz - może to zrobić z pojedynczym wyborem lub z wielokrotnym wyborem. Dba o to, co musisz zrobić - śledzić stan związany z każdym identyfikatorem wiersza (NIE indeks wiersza, ponieważ mogą one ulec zmianie, jeśli dane w bazie danych ulegną zmianie). Aby wyświetlić ten stan, należy poprawnie interweniować w interfejsie użytkownika w widoku listy, wykonując funkcję Checkable w widoku najwyższego poziomu w elemencie listy. Istnieją próbki tego w ApiDemos. – hackbod

+0

Dzięki hackbod, ale myślę, że to jeszcze bardziej mnie zdezorientowało, głównie dlatego, że właśnie to robię. Widok listy elementów mojego listView najwyższego poziomu jest możliwą do sprawdzenia klasą wpisu, dla której opublikowałem kod powyżej. Uniknąłem przesłonięcia metody performclick() w przeszłości i wykorzystałem metodę ListView.setItemChecked() w mojej instancji listview. To jednak nie zadziałało, a widoki nie zmieniłyby tła tych elementów mojego stanu sprawdzanego przez stan (nawet jeśli inne stany wyświetlały się poprawnie). Czy jest to osobny problem, czy jest to powiązane? –

3

Nie sądzę, że problem pochodzi z powyższego kodu. Miałem już ten problem i miało to związek z recyklingiem widoków w liście. Może się tak zdarzyć, jeśli twoja lista będzie nadal wyświetlana na ekranie. Jeśli tak jest, dobrym sposobem, aby to naprawić, jest zapisywanie stanów pozycji na liście, aby można było je śledzić i oprzeć swoje stany na utworzonej liście. Aby uzyskać więcej informacji na temat recyklingu widoków, zobacz this i this.

0

Robiąc coś podobnego w dość złożonym zestawie listView, dodałem do adaptera dodatkową listę i dodałem do niej pozycję klikniętych elementów. getView (...) następnie nadmuchuje/przetwarza widok i tuż przed jego zakończeniem sprawdza stan elementu i stan wewnętrznej karty, aby zdecydować, które tło zastosować.

Ustawiam również plik xml listy stanów, aby tło było przezroczyste, gdy jest wciśnięte, więc selektor jest widoczny, działa to naprawdę dobrze.