Android Endless List

Respostas:

269

Uma solução é implementar OnScrollListenere fazer alterações (como adicionar itens etc.) ao ListAdapterestado conveniente em seu onScrollmétodo.

A seguir, é ListActivityexibida uma lista de números inteiros, começando com 40, adicionando itens quando o usuário rola para o final da lista.

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

Obviamente, você deve usar threads separados para ações de longa duração (como carregar dados da Web) e pode indicar progresso no último item da lista (como o mercado ou os aplicativos do Gmail).

Josef Pfleger
fonte
3
huh, isso não ativaria o tamanho do adaptador se você parasse de rolar no meio da tela? ele deve carregar apenas os próximos 10 quando realmente chegar ao final da lista.
Matthias
9
Percebo muitos exemplos como este usando o BaseAdapter. Queria mencionar que, se você estiver usando um banco de dados, tente usar um SimpleCursorAdapter. Quando você precisar adicionar à lista, atualize seu banco de dados, obtenha um novo cursor e use SimpleCursorAdapter # changeCursor junto com notifyDataSetChanged. Não é necessária nenhuma subclasse e ele tem um desempenho melhor que um BaseAdapter (novamente, apenas se você estiver usando um banco de dados).
brack
1
Depois de chamar notifyDataSetChanged (), ele ficará no topo da lista, como posso evitá-lo?
desenhar
2
Uma observação - usei essa abordagem, mas precisava adicionar uma verificação de quando visibleCount é 0. Para cada lista, recebo um retorno de chamada no onScroll onde firstVisible, visibleCount e totalCount são todos 0, o que tecnicamente atende ao condicional, mas não é quando eu quero carregar mais.
Eric moinho
5
Outro problema com esse código é que a condição firstVisible + visibleCount >= totalCounté atendida várias vezes com várias chamadas para o ouvinte. Se a função carregar mais for uma solicitação da Web (provavelmente será), adicione outra verificação para verificar se uma solicitação está em andamento ou não. Por outro lado, verifique se o totalCount não é igual à contagem total anterior, porque não precisamos de várias solicitações para o mesmo número de itens na lista.
Subin Sebastian
94

Só queria contribuir com uma solução que eu usei para o meu aplicativo.

Também é baseado na OnScrollListenerinterface, mas achei que ele tinha um desempenho de rolagem muito melhor em dispositivos low-end, pois nenhum dos cálculos de contagem total / visível é realizado durante as operações de rolagem.

  1. Deixe seu ListFragmentou ListActivityimplementeOnScrollListener
  2. Adicione os seguintes métodos a essa classe:

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

    onde currentPageé a página da sua fonte de dados que deve ser adicionada à sua lista e thresholdo número de itens da lista (contados no final) que, se visíveis, acionam o processo de carregamento. Se você definir thresholda 0, por exemplo, o usuário tem que rolar até o fim da lista, a fim de carregar mais itens.

  3. (opcional) Como você pode ver, a "verificação de carregar mais" é chamada apenas quando o usuário para de rolar. Para melhorar a usabilidade, você pode aumentar e adicionar um indicador de carregamento ao final da lista via listView.addFooterView(yourFooterView). Um exemplo para essa visualização de rodapé:

    <?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. (opcional) Por fim, remova esse indicador de carregamento chamando listView.removeFooterView(yourFooterView)se não houver mais itens ou páginas.

saschoar
fonte
Eu tentei isso, mas não funciona para ListFragment. Como ele pode ser usado no ListFragment.
21413 z133:
Bem, eu também o uso ListFragmentsem problemas - basta seguir cada uma das etapas acima e você ficará bem;) Ou você poderia fornecer alguma mensagem de erro?
Saschoar
O que você poderia ter perdido é explicado nesta resposta SO: stackoverflow.com/a/6357957/1478093 -getListView().setOnScrollListener(this)
TBieniek
7
note: adicionar e remover rodapé no listview é problemático (o rodapé não aparece se setAdapter é chamado antes de adicionar o rodapé). Acabei modificando a visibilidade da exibição do rodapé diretamente, sem removê-lo.
Dvd
O que deve ser adicionado no loadElements (currentPage) ??? Quero dizer, eu chamo meu AsyncTask ou um thread?
android_developer 28/11
20

Você pode detectar o final da lista com a ajuda de onScrollListener , o código de trabalho é apresentado abaixo:

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

Outra maneira de fazer isso (adaptador interno) é a seguinte:

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

Esteja ciente de que esse método será chamado várias vezes; portanto, você precisa adicionar outra condição para bloquear várias chamadas de addMoreData().

Ao adicionar todos os elementos à lista, ligue para notifyDataSetChanged () dentro do seu adaptador para atualizar o View (ele deve ser executado no encadeamento da UI - runOnUiThread )

Dariusz Bacinski
fonte
7
É uma prática ruim fazer outras coisas além de retornar uma visualização getView. Por exemplo, você não tem garantia de que posé visualizado pelo usuário. Pode ser simplesmente material de medição do ListView.
Thomas Ahle
Quero carregar mais itens após a rolagem de 10 itens. mas percebo que o carregamento começa antes do 10º item ser visível significa no 9º item. Então, como parar isso?
Atul Bhardwaj
4

Na Ognyan Bankov GitHubeu encontrei uma solução simples e funcional!

Ele faz uso do Volley HTTP libraryque torna a rede para aplicativos Android mais fácil e, o mais importante, mais rápida. O Volley está disponível no repositório AOSP aberto.

O código fornecido demonstra:

  1. ListView, preenchido por solicitações paginadas HTTP.
  2. Uso do NetworkImageView.
  3. Paginação ListView "sem fim" com leitura antecipada.

Para uma consistência futura, peguei o repo do Bankov .

oikonomopo
fonte
2

Aqui está uma solução que também facilita a exibição de uma exibição de carregamento no final do ListView durante o carregamento.

Você pode ver as aulas aqui:

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java - Auxiliar para tornar possível o uso do recursos sem estender-se de SimpleListViewWithLoadingIndicator.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java - Ouvinte que inicia o carregamento de dados quando o usuário está prestes a chegar ao final do ListView.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java - The EndlessListView. Você pode usar essa classe diretamente ou estender a partir dela.

Fernando Camargo
fonte
1

Pode ser um pouco tarde, mas a seguinte solução aconteceu muito útil no meu caso. De certa forma, tudo o que você precisa fazer é adicionar ao seu ListView a Footere criar para ele addOnLayoutChangeListener.

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

Por exemplo:

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

Este evento é acionado uma vez quando um rodapé se torna visível e funciona como um encanto.

tehcpu
fonte
0

A chave desse problema é detectar o evento carregar mais, iniciar uma solicitação assíncrona de dados e atualizar a lista. Também é necessário um adaptador com indicador de carga e outros decoradores. De fato, o problema é muito complicado em alguns casos extremos. Apenas uma OnScrollListenerimplementação não é suficiente, porque às vezes os itens não preenchem a tela.

Eu escrevi um pacote pessoal que suporta uma lista sem fim RecyclerViewe também fornece uma implementação de carregador assíncrono AutoPagerFragmentque facilita muito a obtenção de dados de uma fonte de várias páginas. Ele pode carregar qualquer página desejada em um RecyclerViewevento personalizado, não apenas na próxima página.

Aqui está o endereço: https://github.com/SphiaTower/AutoPagerRecyclerManager

ProtossShuttle
fonte
O link está quebrado.
DSlomer64
0

A melhor solução até agora que eu vi está na biblioteca FastAdapter para visualizações de recicladores. Tem um EndlessRecyclerOnScrollListener.

Aqui está um exemplo de uso: EndlessScrollListActivity

Depois que o usei para uma lista de rolagem interminável, percebi que a configuração é muito robusta. Eu definitivamente recomendo.

Shubham Chaudhary
fonte
0

Eu tenho trabalhado em outra solução muito semelhante a isso, mas estou usando a footerViewpara dar ao usuário a possibilidade de baixar mais elementos clicando em footerView, estou usando um "menu" que é mostrado acima ListViewe na parte inferior do Na visualização principal, esse "menu" oculta a parte inferior do menu ListView. Assim, quando o listViewmenu está rolando desaparece e quando o estado da rolagem está ocioso, o menu aparece novamente, mas quando o usuário rola para o final do menu listView, eu "peço" para saber se o footerViewé mostrado nesse caso, o menu não aparece e o usuário pode ver o footerViewpara carregar mais conteúdo. Aqui o código:

Saudações.

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
fonte
0

Eu sei que é uma pergunta antiga e o mundo do Android mudou para o RecyclerViews, mas para qualquer pessoa interessada, você pode achar esta biblioteca muito interessante.

Ele usa o BaseAdapter usado com o ListView para detectar quando a lista foi rolada para o último item ou quando está sendo rolada para longe do último item.

Ele vem com um exemplo de projeto (apenas 100 linhas de código de atividade) que pode ser usado para entender rapidamente como funciona.

Uso simples:

class Boy{

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

}

Um adaptador para armazenar objetos Boy seria semelhante a:


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

}

Observe como o método getView do BoysAdapter retorna uma chamada para o getViewmétodo da superclasse EndlessAdapter . Isso é 100% essencial.

Agora, para criar o adaptador, faça:

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

O loadMoreitem é um botão ou outro elemento da interface do usuário que pode ser clicado para buscar mais dados do URL. Quando colocado conforme descrito no código, o adaptador sabe exatamente quando mostrar esse botão e quando desativá-lo. Basta criar o botão no seu xml e colocá-lo como mostrado no código do adaptador acima.

Aproveitar.

gbenroscience
fonte