Niekończąca się lista Androida

232

Jak mogę utworzyć listę, w której po osiągnięciu końca listy otrzymam powiadomienie, żebym mógł załadować więcej elementów?

Isaac Waller
źródło
9
Polecam EndlessAdapter CommonWare: github.com/commonsguy/cwac-endless . Znalazłem to z tej odpowiedzi na inne pytanie: stackoverflow.com/questions/4667064/… .
Tyler Collier
Potrzebuję tego samego ... czy może ktoś pomóc? .. stackoverflow.com/questions/28122304/…

Odpowiedzi:

269

Jednym z rozwiązań jest wdrożenie OnScrollListeneri wprowadzenie zmian (takich jak dodawanie elementów itp.) ListAdapterW dogodnym stanie w tej onScrollmetodzie.

Poniżej ListActivityprzedstawiono listę liczb całkowitych, zaczynając od 40, dodając elementy, gdy użytkownik przewija do końca listy.

public class Test extends ListActivity implements OnScrollListener {

    Aleph0 adapter = new Aleph0();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(adapter); 
        getListView().setOnScrollListener(this);
    }

    public void onScroll(AbsListView view,
        int firstVisible, int visibleCount, int totalCount) {

        boolean loadMore = /* maybe add a padding */
            firstVisible + visibleCount >= totalCount;

        if(loadMore) {
            adapter.count += visibleCount; // or any other amount
            adapter.notifyDataSetChanged();
        }
    }

    public void onScrollStateChanged(AbsListView v, int s) { }    

    class Aleph0 extends BaseAdapter {
        int count = 40; /* starting amount */

        public int getCount() { return count; }
        public Object getItem(int pos) { return pos; }
        public long getItemId(int pos) { return pos; }

        public View getView(int pos, View v, ViewGroup p) {
                TextView view = new TextView(Test.this);
                view.setText("entry " + pos);
                return view;
        }
    }
}

Oczywiście powinieneś używać osobnych wątków do długotrwałych działań (takich jak ładowanie danych internetowych) i możesz chcieć wskazać postęp w ostatnim elemencie listy (tak jak robią to aplikacje market lub Gmail).

Josef Pfleger
źródło
3
huh, czy nie spowodowałoby to zwiększenia rozmiaru adaptera, gdybyś przestał przewijać na środku ekranu? powinien załadować kolejne 10 dopiero po osiągnięciu dolnej listy.
Matthias,
9
Zauważam wiele takich przykładów za pomocą BaseAdapter. Chciałem wspomnieć, że jeśli korzystasz z bazy danych, spróbuj użyć SimpleCursorAdapter. Gdy musisz dodać do listy, zaktualizuj bazę danych, uzyskaj nowy kursor i użyj SimpleCursorAdapter # changeCursor wraz z powiadomieniemDataSetChanged. Nie jest wymagane żadne podklasowanie i działa ono lepiej niż BaseAdapter (ponownie, tylko jeśli używasz bazy danych).
nawias
1
Po wywołaniu powiadomieniaDataSetChanged (), znajdzie się na górze listy, jak mogę temu zapobiec?
losowanie
2
Uwaga - zastosowałem to podejście, ale musiałem dodać opcję sprawdzania, gdy parametr visibleCount wynosi 0. Dla każdej listy otrzymuję jedno wywołanie zwrotne do onScroll, gdzie firstVisible, visibleCount i totalCount mają wartość 0, co technicznie spełnia warunek, ale nie jest kiedy chcę załadować więcej.
Eric Mill
5
Innym problemem związanym z tym kodem jest to, że warunek firstVisible + visibleCount >= totalCountjest spełniony wiele razy przy wielu wywołaniach do nasłuchiwania. Jeśli funkcja ładowania więcej jest żądaniem internetowym (najprawdopodobniej będzie), dodaj kolejne sprawdzenie, czy żądanie trwa, czy nie. Z drugiej strony sprawdź, czy totalCount nie jest równy Poprzedniej sumie, ponieważ nie potrzebujemy wielu żądań dla tej samej liczby pozycji na liście.
Subin Sebastian
94

Chciałem tylko udostępnić rozwiązanie, którego użyłem w mojej aplikacji.

Opiera się również na OnScrollListenerinterfejsie, ale zauważyłem, że ma on znacznie lepszą wydajność przewijania na niższych urządzeniach, ponieważ podczas operacji przewijania nie są wykonywane żadne obliczenia widoczne / całkowite zliczanie.

  1. Pozwól ListFragmentlub ListActivitywdrożyćOnScrollListener
  2. Dodaj następujące metody do tej klasy:

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        //leave this empty
    }
    
    @Override
    public void onScrollStateChanged(AbsListView listView, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            if (listView.getLastVisiblePosition() >= listView.getCount() - 1 - threshold) {
                currentPage++;
                //load more list items:
                loadElements(currentPage);
            }
        }
    }

    gdzie currentPageznajduje się strona źródła danych, które należy dodać do listy, i thresholdliczba elementów listy (liczona od końca), które powinny, jeśli są widoczne, uruchomić proces ładowania. Jeśli ustawisz thresholdsię 0, na przykład, użytkownik musi przejść na samym końcu listy, aby załadować więcej rzeczy.

  3. (opcjonalnie) Jak widać, „ładowanie więcej kontroli” jest wywoływane tylko wtedy, gdy użytkownik przestanie przewijać. Aby poprawić użyteczność, możesz napompować i dodać wskaźnik ładowania na końcu listy za pośrednictwem listView.addFooterView(yourFooterView). Jeden przykład takiego widoku stopki:

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/footer_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp" >
    
        <ProgressBar
            android:id="@+id/progressBar1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_gravity="center_vertical" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/progressBar1"
            android:padding="5dp"
            android:text="@string/loading_text" />
    
    </RelativeLayout>
  4. (opcjonalnie) Na koniec usuń ten wskaźnik ładowania, dzwoniąc, listView.removeFooterView(yourFooterView)jeśli nie ma już więcej elementów lub stron.

saschoar
źródło
Próbowałem tego, ale to nie działa dla ListFragment. Jak można go używać w ListFragment.
zeeshan
Cóż, używam go ListFragmentrównież bez żadnych problemów - wystarczy wykonać każdy z powyższych kroków, a wszystko będzie dobrze;) Czy możesz podać jakieś komunikaty o błędach?
saschoar
To, co mogło być pominięte, zostało wyjaśnione w tej odpowiedzi SO: stackoverflow.com/a/6357957/1478093 -getListView().setOnScrollListener(this)
TBieniek
7
Uwaga: dodawanie i usuwanie stopki w widoku listy jest problematyczne (stopka nie wyświetla się, jeśli setAdapter jest wywoływany przed dodaniem stopki), skończyłem modyfikując widoczność widoku stopki bezpośrednio bez jej usuwania.
dvd,
Co należy dodać w loadElements (currentPage) ??? Mam na myśli, czy nazywam moją AsyncTask lub wątek?
android_developer
20

Koniec listy można wykryć przy pomocy onScrollListener , działający kod przedstawiono poniżej:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (view.getAdapter() != null && ((firstVisibleItem + visibleItemCount) >= totalItemCount) && totalItemCount != mPrevTotalItemCount) {
        Log.v(TAG, "onListEnd, extending list");
        mPrevTotalItemCount = totalItemCount;
        mAdapter.addMoreData();
    }
}

Innym sposobem na to (wewnętrzny adapter) jest:

    public View getView(int pos, View v, ViewGroup p) {
            if(pos==getCount()-1){
                addMoreData(); //should be asynctask or thread
            }
            return view;
    }

Pamiętaj, że ta metoda będzie wywoływana wiele razy, więc musisz dodać kolejny warunek, aby zablokować wiele wywołań addMoreData().

Po dodaniu wszystkich elementów do listy należy wywołać powiadomienie DataSetChanged () wewnątrz adaptera, aby zaktualizować widok (powinien być uruchamiany w wątku interfejsu użytkownika - runOnUiThread )

Dariusz Baciński
źródło
7
Złą praktyką jest robienie innych rzeczy niż powrót do widoku getView. Na przykład nie masz gwarancji, że posużytkownik wyświetli. Może to być po prostu pomiar ListView.
Thomas Ahle,
Chcę załadować więcej elementów po przewinięciu 10 elementów. ale zauważam, że ładowanie zaczyna się, zanim 10 element będzie widoczny, oznacza 9 element. Więc jak to zatrzymać?
Atul Bhardwaj
4

W Ognyan Bankov GitHubznalazłem proste i działające rozwiązanie!

Wykorzystuje to, Volley HTTP libraryco sprawia, że ​​tworzenie sieci dla aplikacji na Androida jest łatwiejsze i, co najważniejsze, szybsze. Siatkówka jest dostępna za pośrednictwem otwartego repozytorium AOSP.

Podany kod pokazuje:

  1. ListView, który jest wypełniany przez paginowane żądania HTTP.
  2. Zastosowanie NetworkImageView.
  3. Pagodowanie „Endless” ListView z możliwością odczytu z wyprzedzeniem.

Aby zachować spójność w przyszłości, rozwidliłem repozytorium Bankowa .

oikonomopo
źródło
2

Oto rozwiązanie, które ułatwia wyświetlanie widoku ładowania na końcu ListView podczas ładowania.

Możesz zobaczyć zajęcia tutaj:

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java - Pomocnik umożliwiający korzystanie z funkcje bez rozszerzania z SimpleListViewWithLoadingIndicator.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java - Słuchacz, który rozpoczyna ładowanie danych, gdy użytkownik zbliża się do dolnej części ListView.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java - The EndlessListView. Możesz użyć tej klasy bezpośrednio lub ją rozszerzyć.

Fernando Camargo
źródło
1

Może trochę się spóźnić, ale w moim przypadku bardzo przydatne okazało się następujące rozwiązanie. W ten sposób wszystko, co musisz zrobić, to dodać do ListView a Footeri stworzyć dla niego addOnLayoutChangeListener.

http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)

Na przykład:

ListView listView1 = (ListView) v.findViewById(R.id.dialogsList); // Your listView
View loadMoreView = getActivity().getLayoutInflater().inflate(R.layout.list_load_more, null); // Getting your layout of FooterView, which will always be at the bottom of your listview. E.g. you may place on it the ProgressBar or leave it empty-layout.
listView1.addFooterView(loadMoreView); // Adding your View to your listview 

...

loadMoreView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
         Log.d("Hey!", "Your list has reached bottom");
    }
});

To wydarzenie jest uruchamiane raz, gdy stopka staje się widoczna i działa jak urok.

tehcpu
źródło
0

Kluczem do tego problemu jest wykrycie zdarzenia ładowania obciążenia, uruchomienie żądania asynchronicznego danych, a następnie zaktualizowanie listy. Potrzebny jest również adapter ze wskaźnikiem obciążenia i innymi dekoratorami. W rzeczywistości problem jest bardzo skomplikowany w niektórych przypadkach narożnych. Tylko OnScrollListenerrealizacja nie wystarczy, bo czasami elementy nie wypełnia ekranu.

Napisałem osobisty pakiet, który obsługuje nieskończoną listę RecyclerView, a także zapewnia implementację modułu ładującego asynchron, AutoPagerFragmentktóry bardzo ułatwia pobieranie danych ze źródła wielostronicowego. Może załadować dowolną stronę RecyclerViewna niestandardowe zdarzenie, nie tylko następną stronę.

Oto adres: https://github.com/SphiaTower/AutoPagerRecyclerManager

ProtossShuttle
źródło
Link jest zepsuty.
DSlomer64
0

Najlepsze rozwiązanie, jakie do tej pory widziałem, znajduje się w bibliotece FastAdapter dla widoków recyklerów. Ma EndlessRecyclerOnScrollListener.

Oto przykładowe użycie: EndlessScrollListActivity

Kiedyś użyłem go do nieskończonej przewijanej listy, zdałem sobie sprawę, że konfiguracja jest bardzo solidna. Zdecydowanie polecam.

Shubham Chaudhary
źródło
0

Pracowałem w innym rozwiązaniu bardzo podobnym do tego, ale używam a, footerViewaby dać użytkownikowi możliwość pobrania większej liczby elementów, klikając przycisk footerView, używam „menu” pokazanego powyżej ListViewi na dole widok rodzica, to „menu” ukrywa dolną część ListView, więc gdy listViewprzewijane menu zniknie, a gdy stan przewijania jest bezczynny, menu pojawi się ponownie, ale gdy użytkownik przewinie do końca listView, „pytam”, aby wiedzieć, czy footerVieww takim przypadku jest pokazane, menu nie pojawia się, a użytkownik może zobaczyć, footerViewaby załadować więcej treści. Oto kod:

Pozdrowienia.

listView.setOnScrollListener(new OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub
        if(scrollState == SCROLL_STATE_IDLE) {
            if(footerView.isShown()) {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.VISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            }
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {

    }
});
pabloverd
źródło
0

Wiem, że to stare pytanie, a świat Androida przeszedł w większości na RecyclerViews, ale dla wszystkich zainteresowanych ta biblioteka może być bardzo interesująca.

Wykorzystuje BaseAdapter używany z ListView do wykrywania, kiedy lista została przewinięta do ostatniego elementu lub kiedy jest przewijana od ostatniego elementu.

Zawiera przykładowy projekt (zaledwie 100 wierszy kodu działania), którego można użyć, aby szybko zrozumieć, jak to działa.

Proste użycie:

class Boy{

private String name;
private double height;
private int age;
//Other code

}

Adapter do przechowywania obiektów Boy wyglądałby następująco:


public class BoysAdapter extends EndlessAdapter<Boy>{




        ViewHolder holder = null;


        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent
                    .getContext());

            holder = new ViewHolder();

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


            holder.nameView = convertView.findViewById(R.id.cell);

            // minimize the default image.
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Boy boy = getItem(position);

        try {
            holder.nameView.setText(boy.getName());

            ///Other data rendering codes.

        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.getView(position,convertView,parent);

}

Zwróć uwagę, w jaki sposób metoda getView BoysAdapter zwraca wywołanie do metody nadklasy EndlessAdapter getView. Jest to w 100% niezbędne.

Teraz, aby utworzyć adapter, wykonaj:

   adapter = new ModelAdapter() {
            @Override
            public void onScrollToBottom(int bottomIndex, boolean moreItemsCouldBeAvailable) {

                if (moreItemsCouldBeAvailable) { 
                    makeYourServerCallForMoreItems();
                } else {
                    if (loadMore.getVisibility() != View.VISIBLE) {
                        loadMore.setVisibility(View.VISIBLE);
                    }
                }
            }

            @Override
            public void onScrollAwayFromBottom(int currentIndex) { 
                loadMore.setVisibility(View.GONE);
            }

            @Override
            public void onFinishedLoading(boolean moreItemsReceived) { 
                if (!moreItemsReceived) {
                    loadMore.setVisibility(View.VISIBLE);
                }
            }
        };

loadMorePozycja jest przycisk lub inny element interfejsu użytkownika, które można kliknąć, aby pobrać więcej danych z adresu URL. Po umieszczeniu zgodnie z opisem w kodzie adapter dokładnie wie, kiedy pokazać ten przycisk, a kiedy go wyłączyć. Po prostu utwórz przycisk w swoim pliku XML i umieść go tak, jak pokazano w powyższym kodzie adaptera.

Cieszyć się.

gbenroscience
źródło