Jak działa mechanizm recyklingu ListView

146

Więc mam ten problem miałem przed i naturalnie poprosiłem o pomoc na tutaj . Odpowiedź Luksprog była świetna, ponieważ nie miałem pojęcia, jak ListView i GridView zoptymalizowały się dzięki recyklingowi widoków. Dzięki jego radom mogłem zmienić sposób dodawania widoków do mojego widoku GridView. Problem w tym, że mam teraz coś, co nie ma sensu. To jest moje getViewz mojego BaseAdapter:


public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}

Problem polega na tym, że kiedy przewijam, dzieje się tak, a nie na pozycji 0 ... Wygląda na to, że pozycja 6 i pozycja 8, plus umieszcza dwie na pozycji 8. Teraz wciąż próbuję zawiesić korzystanie z ListView i GridView, więc robię nie rozumiem, dlaczego tak się dzieje. Jednym z głównych powodów , dla których zadaję to pytanie, jest pomoc innym, którzy prawdopodobnie nie wiedzą o ListView i widoku recyklingu GridView lub o mechanizmie ScrapView, jak to ujęto w tym artykule .

wprowadź opis obrazu tutaj

Później Edytuj

Dodanie linku do rozmowy Google IO, czyli w zasadzie wszystko, czego potrzebujesz, aby zrozumieć, jak działa ListView. Link nie zdawał sobie sprawy z komentarzy. Więc user3427079 był na tyle miły, że zaktualizował ten link. Tutaj jest dla łatwego dostępu.

Andy
źródło
Haha, dzięki za ten link. Zacząłem oglądać to teraz :)
Andy
Nie zaimplementowałeś w pełni kodu z poprzedniego pytania. Chodzi o to, że zawsze potrzebujesz fragmentu kodu, aby cofnąć zmiany wprowadzone dla innych wierszy (w twoim przypadku pozycji 0).
Luksprog
Cóż, rozumiem, że @Luksprog Ale czy to wyjaśnia, dlaczego umieszcza także Widok na innych pozycjach? Jeśli już, spodziewałem się, że tylko pozycja 0 będzie miała zduplikowane widoki. Idk, wciąż nie rozumiem, w jaki sposób ListView wiąże te rzeczy: /
Andy,
1
Recykling oznacza, że ​​widok wiersza, który właśnie zniknął (na przykład pozycja 0, po przewinięciu o jeden wiersz w dół) z ekranu, może zostać użyty później, gdy ListViewpojawi się potrzeba nowego wiersza (np. Kontynuowanie przewijania w dół / w górę). Problem w tym, że widok, który po prostu zniknął i będzie używany na przyszłych potrzebnych stanowiskach, TextViewjuż do niego dodano, to jest problem. Aby go rozwiązać, musisz usunąć go z getViewmetody, jeśli getViewmetoda jest wywoływana dla innej pozycji niż 0.
Luksprog
2
Link do górnego posta jest martwy, myślę, że to jest to? youtube.com/watch?v=N6YdwzAvwOA
G_V

Odpowiedzi:

330

Początkowo nie zdawałem sobie sprawy z recyklingu listview i mechanizmu użycia convertview, ale po całodniowych badaniach prawie rozumiem mechanizmy widoku listy, odwołując się do obrazu z android.amberfog wprowadź opis obrazu tutaj

Za każdym razem, gdy widok listy jest wypełniany przez adapter, w zasadzie pokazuje liczbę wierszy, które widok listy może wyświetlić na ekranie, a liczba wierszy nie zwiększa się nawet podczas przewijania listy. To jest sztuczka, której używa Android, aby wyświetlić listę działającą wydajniej i szybciej. Teraz wewnętrzna historia listview odnosząca się do obrazu, jak widać, początkowo listview ma 7 widocznych elementów, a następnie, jeśli przewiniesz w górę, aż element 1 nie będzie już widoczny, getView () przekaże ten widok (tj. Element1) do recykler i możesz użyć

System.out.println("getview:"+position+" "+convertView);

wewnątrz twojego

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);
        
        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);
        
        row.setTag(holder);
        
    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

Zauważysz, że w swoim logcat początkowo convertview ma wartość null dla wszystkich widocznych wierszy, ponieważ początkowo w recyklerze nie było żadnych widoków (tj. Elementów), więc funkcja getView () tworzy nowy widok dla każdego z widocznych elementów, ale gdy przewiniesz w górę i pozycja 1 wyjdzie z ekranu, zostanie wysłana do Recyklera ze swoim obecnym stanem (na przykład tekst TextView lub w moim przypadku, jeśli pole wyboru jest zaznaczone, zostanie skojarzone z widokiem i przechowywane w recyklerze).

Teraz, kiedy przewiniesz w górę / w dół, twój widok listy nie utworzy nowego widoku, użyje widoku, który jest w twoim recyklerze. W swojej logcat można zauważyć, że „convertView” nie jest null, jej, ponieważ nowa pozycja 8 zostanie opracowany przy użyciu convertview, czyli w zasadzie zajmuje pozycję 1 widok z recyklera i nadmuchuje pkt 8 na swoim miejscu, i można obserwować to w moim kodzie. Jeśli miałeś pole wyboru i jeśli zaznaczysz je na pozycji 0 (powiedzmy, że item1 miał checkbox i zaznaczyłeś je), więc kiedy przewiniesz w dół, zobaczysz pole wyboru item 8 już zaznaczone, to dlatego listview ponownie używa tego samego widoku, nie tworzy nowego dla Ciebie ze względu na optymalizację wydajności.

Ważne sprawy

1 . Nigdy nie ustawiaj layout_heightand layout_widthof your listview na, wrap_contentponieważ getView()zmusi twój adapter do uzyskania dziecka do pomiaru wysokości widoków do narysowania w widoku listy i może spowodować nieoczekiwane zachowanie, takie jak zwracanie Convertview, nawet jeśli lista nie jest przewijana. Zawsze używaj match_parentlub naprawione szerokość wysokość.

2 . Jeśli chcesz użyć jakiegoś układu lub widok po widoku listy i pytanie może przyszedł w swoim umyśle, jeśli ustawić layout_heightdo fill_parentwidoku po widoku listy nie pojawi się, jak to idzie w dół ekranu, tak że lepiej postawić ListView wewnątrz Na przykład układ liniowy i ustaw wysokość i szerokość tego układu zgodnie z wymaganiami i ustaw atrybut wysokości i szerokości widoku listy na stan układu (np. jeśli szerokość układu wynosi 320, a wysokość 280), to widok listy powinny mieć taką samą wysokość i szerokość. To powie getView () o dokładnej wysokości i szerokości widoków do renderowania, a getView () nie będzie wywoływać raz po raz niektórych losowych wierszy i innych problemów, takich jak zwracanie widoku konwersji nawet przed przewijaniem, mam test to ja, chyba że mój widok listy znajdował się wewnątrz lineaLayout, miał również problemy, takie jak powtarzanie wywołania widoku i konwersja widoku jako, umieszczenie Listview wewnątrz LinearLayout działało dla mnie jak magia. (nie wiedziałem dlaczego)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

Ale teraz jest już rozwiązany, wiem, nie jestem dobry w wyjaśnianiu, ale ponieważ poświęcam cały dzień na zrozumienie, więc pomyślałem, że inni początkujący, tacy jak ja, mogą uzyskać pomoc z mojego doświadczenia i mam nadzieję, że teraz wy trochę zrozumiecie z ListView ramach, jak to działa, jak to jest naprawdę brudny i znalazł zbyt wiele problemu ze zrozumieniem go tak skomplikowane początkujących

Muhammad Babar
źródło
36
„Wiem, że nie jestem zbyt dobry w wyjaśnianiu” >>> +37 głosów na tę odpowiedź oznacza, że ​​wykonałeś świetną robotę w wyjaśnianiu. Prosimy o dalsze dzielenie się swoją wiedzą! ;)
Patrz
1
Świetna odpowiedź raja ji +1 ode mnie .. :)
Ehsan Sajjad
2
Dziękuję za obraz. Po wielu kłopotach z ListView, kiedy zaczynałem z Androidem, znałem już zasadę recyklingu po tym, jak bardzo się z nim zmagałem. Mimo to wziął to zdjęcie i zdanie: if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization.abym zrozumiał mój błąd. Najlepszy post o recyklingu Android ListView, na jaki trafiłem!
Kevin Cruijssen,
1
@MuhammadBabar Tak, opublikowałem pytanie: stackoverflow.com/q/30122027/1318946
Pratik Butani
1
@MuhammadBabar niesamowity człowiek wyjaśniający! Oszczędza mój czas. Chcę tylko wiedzieć, czy RecyclerViewdziała również na tym samym mechanizmie? W moim pytaniu stackoverflow.com/questions/47897770/… , wiem, że zadanie jest ciche i niemożliwe listView. Powinienem spróbować recyclerView?
Shambhu,
8

Uważaj, we wzorcu Holder, jeśli ustawiasz pozycję w swoim obiekcie Holder, powinieneś ustawić ją za każdym razem, na przykład:

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

to jest przykład z klasy abstrakcyjnej, gdzie

getHolder(position, view, parent);

wykonuje wszystkie operacje ustawień dla

ImageViews, TextViews, etc..
Ahmed Adel Ismail
źródło
1
Nie zauważyłem tego przez cały ten czas. parent.setTag(holder);zamiast poprawnego sposobu, który jestview.setTag(holder);
ArtiomLK
2

Używając wzoru Holder możesz osiągnąć to, co chcesz:

Opis tego wzoru można znaleźć tutaj:

Recykling widoku listy ma miejsce, gdy przewijasz ekran w dół, a elementy widoku listy powyżej są ukryte. Są one ponownie wykorzystywane do wyświetlania nowych elementów widoku listy.

Kryszna
źródło