Descubra se ListView é rolado para a parte inferior?

105

Posso descobrir se meu ListView foi rolado para a parte inferior? Com isso quero dizer que o último item está totalmente visível.

Fhucho
fonte

Respostas:

171

Editado :

Como estive investigando esse assunto específico em uma de minhas aplicações, posso escrever uma resposta extensa para futuros leitores dessa questão.

Implementar um OnScrollListener, definir as suas ListView's onScrollListenere então você deve ser capaz de lidar com as coisas corretamente.

Por exemplo:

private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);

// ... ... ...

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

    switch(lw.getId()) 
    {
        case R.id.your_list_id:     

            // Make your calculation stuff here. You have all your
            // needed info from the parameters of this function.

            // Sample calculation to determine if the last 
            // item is fully visible.
            final int lastItem = firstVisibleItem + visibleItemCount;

            if(lastItem == totalItemCount)
            {
                if(preLast!=lastItem)
                {
                    //to avoid multiple calls for last item
                    Log.d("Last", "Last");
                    preLast = lastItem;
                }
            }
    }
}
Wroclai
fonte
26
Isso não detectará se o último item está totalmente visível.
fhucho
1
Se você tiver cabeçalhos ou rodapés em sua exibição de lista, você também deve levar em conta isso.
Martin Marconcini,
5
@Wroclai Isto não detectará se o último item está totalmente visível.
Gaurav Arora
Eu estava procurando por algum código de FUNCIONAMENTO semelhante .. Funcionou !! Muito obrigado!!
Suraj Dubey
Não quero começar uma nova pergunta, mas o que faço se a minha listviewfor stackFromBottom? Eu tentei, if (0 == firstVisibleItem){//listviewtop}mas está sendo chamado repetidamente.
shadyinside
68

Resposta tardia, mas se você simplesmente deseja verificar se o ListView está rolado para baixo ou não, sem criar um ouvinte de evento, você pode usar esta instrução if:

if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
    yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
    //It is scrolled all the way down here

}

Primeiro, ele verifica se a última posição possível está à vista. Em seguida, ele verifica se a parte inferior do último botão está alinhada com a parte inferior do ListView. Você pode fazer algo semelhante para saber se está no topo:

if (yourListView.getFirstVisiblePosition() == 0 &&
    yourListView.getChildAt(0).getTop() >= 0)
{
    //It is scrolled all the way up here

}
Martijn
fonte
4
Obrigado. Só precisa mudar ... getChildAt (yourListView.getCount () -1) ... para ... getChildAt (yourListView.getChildCount () -1) ...
OferR
@OferR Não tenho certeza, mas acho que getChildCount()retorna as visualizações no grupo de visualizações, que com a reciclagem de visualizações não é o mesmo que o número de itens no adaptador. No entanto, como ListView desce de AdapterView, você pode usar getCount()diretamente no ListView.
William T. Mallard
@ William T. Mallard Sua primeira frase está correta, e isso é exatamente o que queremos: A última Visualização exibida no grupo. Isso é para verificar se está totalmente visível. (Considere um ListView com 20 linhas, mas apenas as últimas 8 são exibidas. Queremos obter a 8ª visualização no ViewGroup, não a 20ª, que não existe)
OferR
observe que esta solução não funcionará se você estiver atualizando rapidamente a exibição de lista.
esquilo de
19

Do jeito que eu fiz:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE 
            && (listView.getLastVisiblePosition() - listView.getHeaderViewsCount() -
            listView.getFooterViewsCount()) >= (adapter.getCount() - 1)) {

        // Now your listview has hit the bottom
        }
    }

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

    }
});
t-gao
fonte
1
É incrivel! Obrigado
shariful islão
10

Algo no sentido de:

if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
Chris Stewart
fonte
1
Isso não detectará se o último item está totalmente visível.
Duanniston Cardoso Cabral
6
public void onScrollStateChanged(AbsListView view, int scrollState)        
{
    if (!view.canScrollList(View.SCROLL_AXIS_VERTICAL) && scrollState == SCROLL_STATE_IDLE)    
    {
        //When List reaches bottom and the list isn't moving (is idle)
    }
}

Isso funcionou para mim.

Janwilx72
fonte
1
O método view.canScrollList é apenas API 19+, infelizmente
ozmank
Funciona como um encanto
pavaniiitn
4

Isso pode ser

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // TODO Auto-generated method stub

                if (scrollState == 2)
                    flag = true;
                Log.i("Scroll State", "" + scrollState);
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                // TODO Auto-generated method stub
                if ((visibleItemCount == (totalItemCount - firstVisibleItem))
                        && flag) {
                    flag = false;



                    Log.i("Scroll", "Ended");
                }
            }
senhor preguiçoso
fonte
3

Foi muito doloroso lidar com a rolagem, detectar quando ela terminou e realmente está no final da lista (não na parte inferior da tela visível), e aciona meu serviço apenas uma vez, para buscar dados da web. No entanto, está funcionando bem agora. O código é o seguinte para o benefício de qualquer pessoa que enfrente a mesma situação.

NOTA: Tive de mover meu código relacionado ao adaptador para onViewCreated em vez de onCreate e detectar a rolagem principalmente assim:

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

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (getListView().getLastVisiblePosition() == (adapter.getCount() - 1))
        if (RideListSimpleCursorAdapter.REACHED_THE_END) {
            Log.v(TAG, "Loading more data");
            RideListSimpleCursorAdapter.REACHED_THE_END = false;
            Intent intent = new Intent(getActivity().getApplicationContext(), FindRideService.class);
            getActivity().getApplicationContext().startService(intent);
        }
}

Aqui, RideListSimpleCursorAdapter.REACHED_THE_END é uma variável adicional em meu SimpleCustomAdapter que é definida assim:

if (position == getCount() - 1) {
      REACHED_THE_END = true;
    } else {
      REACHED_THE_END = false;
    }

Somente quando essas duas condições se encontram, isso significa que estou realmente no final da lista e que meu serviço será executado apenas uma vez. Se eu não pegar o REACHED_THE_END, mesmo rolar para trás aciona o serviço novamente, desde que o último item esteja à vista.

Zeeshan
fonte
Eu me livrei do else porque estava me causando problemas, mas como uma boa resposta
Mike Baglio Jr.
3

canScrollVertically(int direction)funciona para todas as visualizações e parece fazer o que você pediu, com menos código do que a maioria das outras respostas. Insira um número positivo e, se o resultado for falso, você está na parte inferior.

ie:

if (!yourView.canScrollVertically(1)) { //you've reached bottom }

Joel H
fonte
2

Para expandir um pouco sobre uma das respostas acima, isso é o que eu tive que fazer para que funcionasse completamente. Parece haver cerca de 6 dp de preenchimento integrado dentro de ListViews, e onScroll () estava sendo chamado quando a lista estava vazia. Isso lida com ambas as coisas. Provavelmente poderia ser um pouco otimizado, mas foi escrito mais para maior clareza.

Nota lateral: Eu tentei várias técnicas diferentes de conversão de dp em pixel, e esta dp2px () tem sido a melhor.

myListView.setOnScrollListener(new OnScrollListener() {
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (visibleItemCount > 0) {
            boolean atStart = true;
            boolean atEnd = true;

            View firstView = view.getChildAt(0);
            if ((firstVisibleItem > 0) ||
                    ((firstVisibleItem == 0) && (firstView.getTop() < (dp2px(6) - 1)))) {
                // not at start
                atStart = false;
            }

            int lastVisibleItem = firstVisibleItem + visibleItemCount;
            View lastView = view.getChildAt(visibleItemCount - 1);
            if ((lastVisibleItem < totalItemCount) ||
                    ((lastVisibleItem == totalItemCount) &&
                            ((view.getHeight() - (dp2px(6) - 1)) < lastView.getBottom()))
                    ) {
                        // not at end
                    atEnd = false;
                }

            // now use atStart and atEnd to do whatever you need to do
            // ...
        }
    }
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }
});

private int dp2px(int dp) {
    return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
CSX321
fonte
2

Não posso comentar ainda porque não tenho reputação suficiente, mas na resposta de @Ali Imran e @Wroclai acho que falta algo. Com esse pedaço de código, uma vez que você atualize o preLast, ele nunca mais executará o Log. No meu problema específico, desejo executar alguma operação toda vez que rolar para o final, mas depois que preLast for atualizado para LastItem, essa operação nunca será executada novamente.

private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);

// ... ... ...

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

switch(lw.getId()) {
    case android.R.id.list:     

        // Make your calculation stuff here. You have all your
        // needed info from the parameters of this function.

        // Sample calculation to determine if the last 
        // item is fully visible.
         final int lastItem = firstVisibleItem + visibleItemCount;
       if(lastItem == totalItemCount) {
          if(preLast!=lastItem){ //to avoid multiple calls for last item
            Log.d("Last", "Last");
            preLast = lastItem;
          }
       } else {
            preLast = lastItem;
}

}

Com esse "outro", agora você pode executar seu código (Log, neste caso) toda vez que rolar para o final novamente.

Teno Gonzalez Dos Santos
fonte
2
public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        int lastindex = view.getLastVisiblePosition() + 1;

        if (lastindex == totalItemCount) { //showing last row
            if ((view.getChildAt(visibleItemCount - 1)).getTop() == view.getHeight()) {
                //Last row fully visible
            }
        }
    }
John Ruban Singh
fonte
1

Para que sua lista chame quando a lista chegar pela última vez e se ocorrer um erro, isso não chamará o endoflistview novamente . Este código também ajudará neste cenário.

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
        int visibleItemCount, int totalItemCount) {
    final int lastPosition = firstVisibleItem + visibleItemCount;
    if (lastPosition == totalItemCount) {
        if (previousLastPosition != lastPosition) { 

            //APPLY YOUR LOGIC HERE
        }
        previousLastPosition = lastPosition;
    }
    else if(lastPosition < previousLastPosition - LIST_UP_THRESHOLD_VALUE){
        resetLastIndex();
    }
}

public void resetLastIndex(){
    previousLastPosition = 0;
}

onde o LIST_UP_THRESHOLD_VALUE pode ser qualquer valor inteiro (eu usei 5) onde sua lista é rolada para cima e ao retornar ao final, isso chamará o fim da exibição de lista novamente.

Vyshnavi
fonte
1

Eu descobri uma maneira muito boa de carregar automaticamente o próximo conjunto de páginas de uma maneira que não exija a sua ScrollView(como a resposta aceita exige).

Em ParseQueryAdapter, há um método chamado getNextPageViewque permite que você forneça sua própria visualização personalizada que aparece no final da lista quando há mais dados para carregar, de modo que só será acionado quando você atingir o final do conjunto de páginas atual (é a visualização "carregar mais .." por padrão). Este método é chamado quando há mais dados para carregar, por isso é um ótimo lugar para chamar. loadNextPage(); Desta forma, o adaptador faz todo o trabalho duro para você determinar quando os novos dados devem ser carregados e não será chamado se você tiver chegou ao fim do conjunto de dados.

public class YourAdapter extends ParseQueryAdapter<ParseObject> {

..

@Override
public View getNextPageView(View v, ViewGroup parent) {
   loadNextPage();
   return super.getNextPageView(v, parent);
  }

}

Então dentro de sua atividade / fragmento você só precisa configurar o adaptador e novos dados serão atualizados automaticamente para você como mágica.

adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Max Worg
fonte
1

Para detectar se o último item está totalmente visível , você pode simplesmente adicionar cálculos na parte inferior do último item visível da visualização por lastItem.getBottom().

yourListView.setOnScrollListener(this);   

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

    int vH = view.getHeight();
    int topPos = view.getChildAt(0).getTop();
    int bottomPos = view.getChildAt(visibleItemCount - 1).getBottom();

    switch(view.getId()) {
        case R.id.your_list_view_id:
            if(firstVisibleItem == 0 && topPos == 0) {
                //TODO things to do when the list view scroll to the top
            }

            if(firstVisibleItem + visibleItemCount == totalItemCount 
                && vH >= bottomPos) {
                //TODO things to do when the list view scroll to the bottom
            }
            break;
    }
}
Vai
fonte
1

Eu fui com:

@Override
public void onScroll(AbsListView listView, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
    if(totalItemCount - 1 == favoriteContactsListView.getLastVisiblePosition())
    {
        int pos = totalItemCount - favoriteContactsListView.getFirstVisiblePosition() - 1;
        View last_item = favoriteContactsListView.getChildAt(pos);

        //do stuff
    }
}
Goran Horia Mihail
fonte
1

No método getView()(de uma BaseAdapterclasse derivada) pode-se verificar se a posição da visão atual é igual à lista de itens na Adapter. Se for esse o caso, significa que chegamos ao fim / fim da lista:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // ...

    // detect if the adapter (of the ListView/GridView) has reached the end
    if (position == getCount() - 1) {
        // ... end of list reached
    }
}
Ramon
fonte
0

Eu acho uma maneira melhor de detectar o fim da rolagem do listview na parte inferior, primeiro detecte o fim do scoll por esta
Implementação de onScrollListener para detectar o fim da rolagem em um ListView

 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    this.currentFirstVisibleItem = firstVisibleItem;
    this.currentVisibleItemCount = visibleItemCount;
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.currentScrollState = scrollState;
    this.isScrollCompleted();
 }

private void isScrollCompleted() {
    if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
        /*** In this way I detect if there's been a scroll which has completed ***/
        /*** do the work! ***/
    }
}

finalmente combinar a resposta de Martijn

OnScrollListener onScrollListener_listview = new OnScrollListener() {       

        private int currentScrollState;
        private int currentVisibleItemCount;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // TODO Auto-generated method stub

            this.currentScrollState = scrollState;
            this.isScrollCompleted();
        }

        @Override
        public void onScroll(AbsListView lw, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // TODO Auto-generated method stub
            this.currentVisibleItemCount = visibleItemCount;

        }

        private void isScrollCompleted() {
            if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
                /*** In this way I detect if there's been a scroll which has completed ***/
                /*** do the work! ***/

                if (listview.getLastVisiblePosition() == listview.getAdapter().getCount() - 1
                        && listview.getChildAt(listview.getChildCount() - 1).getBottom() <= listview.getHeight()) {
                    // It is scrolled all the way down here
                    Log.d("henrytest", "hit bottom");
                }


            }
        }

    };
HenryChuang
fonte
0

Muito obrigado aos pôsteres no stackoverflow! Combinei algumas ideias e criei um ouvinte de classe para atividades e fragmentos (portanto, esse código é mais reutilizável, tornando o código mais rápido de escrever e muito mais limpo).

Tudo que você precisa fazer quando você tem minha classe é implementar a interface (e claro, criar um método para ela) que está declarada na minha classe e criar um objeto dessa classe passando argumentos.

/**
* Listener for getting call when ListView gets scrolled to bottom
*/
public class ListViewScrolledToBottomListener implements AbsListView.OnScrollListener {

ListViewScrolledToBottomCallback scrolledToBottomCallback;

private int currentFirstVisibleItem;
private int currentVisibleItemCount;
private int totalItemCount;
private int currentScrollState;

public interface ListViewScrolledToBottomCallback {
    public void onScrolledToBottom();
}

public ListViewScrolledToBottomListener(Fragment fragment, ListView listView) {
    try {
        scrolledToBottomCallback = (ListViewScrolledToBottomCallback) fragment;
        listView.setOnScrollListener(this);
    } catch (ClassCastException e) {
        throw new ClassCastException(fragment.toString()
                + " must implement ListViewScrolledToBottomCallback");
    }
}

public ListViewScrolledToBottomListener(Activity activity, ListView listView) {
    try {
        scrolledToBottomCallback = (ListViewScrolledToBottomCallback) activity;
        listView.setOnScrollListener(this);
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement ListViewScrolledToBottomCallback");
    }
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    this.currentFirstVisibleItem = firstVisibleItem;
    this.currentVisibleItemCount = visibleItemCount;
    this.totalItemCount = totalItemCount;
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.currentScrollState = scrollState;
    if (isScrollCompleted()) {
        if (isScrolledToBottom()) {
            scrolledToBottomCallback.onScrolledToBottom();
        }
    }
}

private boolean isScrollCompleted() {
    if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
        return true;
    } else {
        return false;
    }
}

private boolean isScrolledToBottom() {
    System.out.println("First:" + currentFirstVisibleItem);
    System.out.println("Current count:" + currentVisibleItemCount);
    System.out.println("Total count:" + totalItemCount);
    int lastItem = currentFirstVisibleItem + currentVisibleItemCount;
    if (lastItem == totalItemCount) {
        return true;
    } else {
        return false;
    }
}
}
Janusz Hain
fonte
0

Você precisa adicionar um recurso de rodapé xml vazio ao listView e detectar se esse rodapé está visível.

    private View listViewFooter;
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_newsfeed, container, false);

        listView = (CardListView) rootView.findViewById(R.id.newsfeed_list);
        footer = inflater.inflate(R.layout.newsfeed_listview_footer, null);
        listView.addFooterView(footer);

        return rootView;
    }

Então, em seu listener de rolagem listView você faz isso

@
Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  if (firstVisibleItem == 0) {
    mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.TOP);
    mSwipyRefreshLayout.setEnabled(true);
  } else if (firstVisibleItem + visibleItemCount == totalItemCount) //If last row is visible. In this case, the last row is the footer.
  {
    if (footer != null) //footer is a variable referencing the footer view of the ListView. You need to initialize this onCreate
    {
      if (listView.getHeight() == footer.getBottom()) { //Check if the whole footer is visible.
        mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.BOTTOM);
        mSwipyRefreshLayout.setEnabled(true);
      }
    }
  } else
    mSwipyRefreshLayout.setEnabled(false);
}

Joseph Mhanna
fonte
0

Se você definir uma tag em uma visão do último item da visão de lista, mais tarde você pode recuperar a visão com a marca, se a visão for nula é porque a visão não está mais carregada. Como isso:

private class YourAdapter extends CursorAdapter {
    public void bindView(View view, Context context, Cursor cursor) {

         if (cursor.isLast()) {
            viewInYourList.setTag("last");
         }
         else{
            viewInYourList.setTag("notLast");
         }

    }
}

então, se você precisa saber se o último item foi carregado

View last = yourListView.findViewWithTag("last");
if (last != null) {               
   // do what you want to do
}
Dante Carvalho Costa
fonte
0

Janwilx72 está certo, mas é min sdk é 21, então eu crio este método:

private boolean canScrollList(@ScrollOrientation int direction, AbsListView listView) {
    final int childCount = listView.getChildCount();
    if (childCount == 0) {
        return false;
    }

    final int firstPos = listView.getFirstVisiblePosition();
    final int paddingBottom = listView.getListPaddingBottom();
    final int paddingTop = listView.getListPaddingTop();
    if (direction > 0) {
        final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
        final int lastPos    = firstPos + childCount;
        return lastPos < listView.getChildCount() || lastBottom > listView.getHeight() - paddingBottom;
    } else {
        final int firstTop = listView.getChildAt(0).getTop();
        return firstPos > 0 || firstTop < paddingTop;
    }
}

para ScrollOrientation:

protected static final int SCROLL_UP = -1;
protected static final int SCROLL_DOWN = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SCROLL_UP, SCROLL_DOWN})
protected @interface Scroll_Orientation{}

Talvez tarde, apenas para retardatários。

xiaoyee
fonte
0

Se você estiver usando um adaptador personalizado com seu listview (a maioria das pessoas usa!), Uma bela solução é dada aqui!

https://stackoverflow.com/a/55350409/1845404

O método getView do adaptador detecta quando a lista foi rolada para o último item. Ele também adiciona correção para os raros momentos em que alguma posição anterior é chamada, mesmo depois que o adaptador já renderizou a última visualização.

gbenroscience
fonte
0

Eu fiz isso e funciona para mim:

private void YourListView_Scrolled(object sender, ScrolledEventArgs e)
        {
                double itemheight = YourListView.RowHeight;
                double fullHeight = YourListView.Count * itemheight;
                double ViewHeight = YourListView.Height;

                if ((fullHeight - e.ScrollY) < ViewHeight )
                {
                    DisplayAlert("Reached", "We got to the end", "OK");
                }
}
Ahmed Osman
fonte
-5

Isso rolará para baixo na sua lista até a última entrada.

ListView listView = new ListView(this);
listView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
listView.setStackFromBottom(true);
Salman Khalid
fonte