Scopri se ListView viene fatto scorrere fino in fondo?


105

Posso scoprire se il mio ListView viene fatto scorrere fino in fondo? Con questo intendo che l'ultimo elemento è completamente visibile.

Risposte:


171

Modificato :

Dato che ho indagato su questo particolare argomento in una delle mie applicazioni, posso scrivere una risposta estesa per i futuri lettori di questa domanda.

Implementare un OnScrollListener, impostare le ListView's onScrollListenered allora si dovrebbe essere in grado di gestire le cose correttamente.

Per esempio:

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

26
Questo non rileverà se l'ultimo elemento è completamente visibile.
fhucho

1
Se hai intestazioni o piè di pagina nella tua visualizzazione elenco, devi tenerne conto anche tu.
Martin Marconcini

5
@Wroclai Questo non rileverà se l'ultimo elemento è completamente visibile.
Gaurav Arora

Stavo cercando un codice FUNZIONANTE simile .. Ha funzionato !! Molte grazie!!
Suraj Dubey

Non voglio iniziare una nuova domanda, ma cosa devo fare se la mia listviewè stackFromBottom? Ho provato, if (0 == firstVisibleItem){//listviewtop}ma viene chiamato ripetutamente.
shadyinside

68

Risposta tardiva, ma se desideri semplicemente controllare se il tuo ListView viene fatto scorrere fino in fondo o meno, senza creare un listener di eventi, puoi utilizzare questa istruzione if:

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

}

Per prima cosa controlla se l'ultima posizione possibile è in vista. Quindi controlla se la parte inferiore dell'ultimo pulsante è allineata con la parte inferiore di ListView. Puoi fare qualcosa di simile per sapere se è tutto in alto:

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

}

4
Grazie. Ho solo bisogno di cambiare ... getChildAt (yourListView.getCount () -1) ... a ... getChildAt (yourListView.getChildCount () -1) ...
OferR

@OferR Non sono sicuro, ma penso che getChildCount()restituisca le visualizzazioni nel viewgroup, che con il riciclo delle viste non è uguale al numero di elementi nell'adattatore. Tuttavia, poiché ListView discende da AdapterView, è possibile utilizzarlo getCount()direttamente su ListView.
William T. Mallard,

@ William T. Mallard La tua prima frase è corretta, e questo è esattamente quello che vogliamo: L'ultima vista che mostra nel gruppo. Questo per verificare che sia completamente visibile. (Si consideri un ListView con 20 righe, ma vengono mostrate solo le ultime 8. Vogliamo ottenere l'ottava vista nel ViewGroup, non il 20 che non esiste)
OferR

nota che questa soluzione non funzionerà se stai aggiornando rapidamente la visualizzazione elenco.
scoiattolo

19

Il modo in cui l'ho fatto:

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

    }
});

1
È meraviglioso! Grazie
shariful islam


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

Questo ha funzionato per me.


1
Il metodo view.canScrollList è solo API 19+ purtroppo
ozmank

Funziona a
meraviglia

4

Questo può essere

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

3

È stato piuttosto doloroso gestire lo scorrimento, rilevare quando è finito ed è effettivamente in fondo all'elenco (non in fondo allo schermo visibile) e attiva il mio servizio solo una volta, per recuperare i dati dal web. Tuttavia ora sta funzionando benissimo. Il codice è il seguente a beneficio di chiunque si trovi ad affrontare la stessa situazione.

NOTA: ho dovuto spostare il codice relativo all'adattatore in onViewCreated invece di onCreate e rilevare lo scorrimento principalmente in questo modo:

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

Qui RideListSimpleCursorAdapter.REACHED_THE_END è una variabile aggiuntiva nel mio SimpleCustomAdapter che è impostata in questo modo:

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

Solo quando entrambe queste condizioni soddisfano, significa che sono effettivamente in fondo alla lista e che il mio servizio verrà eseguito solo una volta. Se non prendo REACHED_THE_END, anche lo scorrimento all'indietro attiva nuovamente il servizio, purché l'ultimo elemento sia visualizzato.


Mi sono sbarazzato dell'altro perché mi stava causando problemi, ma nel complesso una buona risposta
Mike Baglio Jr.

3

canScrollVertically(int direction)funziona per tutte le viste e sembra fare quello che hai chiesto, con meno codice rispetto alla maggior parte delle altre risposte. Inserisci un numero positivo e se il risultato è falso, sei in fondo.

vale a dire:

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


2

Per espandere un po 'una delle risposte precedenti, questo è quello che dovevo fare per farlo funzionare completamente. Sembra che ci siano circa 6dp di riempimento incorporato all'interno di ListViews e onScroll () veniva chiamato quando l'elenco era vuoto. Questo gestisce entrambe le cose. Probabilmente potrebbe essere ottimizzato un po ', ma è scritto di più per chiarezza.

Nota a margine: ho provato diverse tecniche di conversione da dp a pixel, e questa dp2px () è stata la migliore.

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

2

Non posso ancora commentare perché non ho abbastanza reputazione, ma nella risposta di @Ali Imran e @Wroclai penso che manchi qualcosa. Con quel pezzo di codice, una volta aggiornato preLast, non eseguirà mai più il log. Nel mio problema specifico, voglio eseguire qualche operazione ogni volta che scorro verso il basso, ma una volta che preLast viene aggiornato a LastItem, quell'operazione non viene mai più eseguita.

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

}

Con quell '"altro" ora puoi eseguire il tuo codice (Log, in questo caso) ogni volta che scorri di nuovo verso il basso.


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

1

Affinché il tuo elenco venga chiamato quando l'elenco raggiunge l'ultimo e se si verifica un errore, questo non chiamerà più endoflistview . Questo codice aiuterà anche questo scenario.

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

dove LIST_UP_THRESHOLD_VALUE può essere qualsiasi valore intero (ho usato 5) in cui l'elenco viene fatto scorrere verso l'alto e mentre si torna alla fine, questo chiamerà di nuovo la fine della visualizzazione elenco.


1

Ho trovato un modo molto carino per caricare automaticamente il set di pagine successivo in un modo che non richiede il tuo ScrollView(come richiede la risposta accettata).

Su ParseQueryAdapter c'è un metodo chiamato getNextPageViewche è lì per permetterti di fornire la tua visualizzazione personalizzata che appare alla fine dell'elenco quando ci sono più dati da caricare, quindi si attiverà solo quando avrai raggiunto la fine del set di pagine corrente (è la visualizzazione "carica altro .." per impostazione predefinita). Questo metodo viene chiamato solo quando ci sono più dati da caricare quindièun ottimo posto da chiamare In loadNextPage(); questo modo l'adattatore fa tutto il lavoro duro per te nel determinare quando i nuovi dati devono essere caricati e non sarà chiamato affatto se lo hai raggiunto la fine del set di dati.

public class YourAdapter extends ParseQueryAdapter<ParseObject> {

..

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

}

Quindi all'interno della tua attività / frammento devi solo impostare l'adattatore ei nuovi dati verranno automaticamente aggiornati per te come per magia.

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

1

Per rilevare se l'ultimo elemento è completamente visibile , puoi semplicemente aggiungere il calcolo sulla parte inferiore dell'ultimo elemento visibile della vista di 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;
    }
}

1

Sono andato con:

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

1

Nel metodo getView()(di una BaseAdapterclasse derivata) si può verificare se la posizione della vista corrente è uguale all'elenco degli elementi nel file Adapter. Se è così, significa che abbiamo raggiunto la fine / il fondo dell'elenco:

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

0

Trovo un modo migliore per rilevare la fine dello scorrimento dell'elenco in basso, prima rileva la fine dello scorrimento con questa
implementazione di onScrollListener per rilevare la fine dello scorrimento in un 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 combina la risposta di 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");
                }


            }
        }

    };

0

Grazie mille ai poster in StackOverflow! Ho combinato alcune idee e creato un listener di classe per attività e frammenti (quindi questo codice è più riutilizzabile rendendo il codice più veloce da scrivere e molto più pulito).

Tutto quello che devi fare quando hai la mia classe è implementare l'interfaccia (e ovviamente creare il metodo per essa) che è dichiarata nella mia classe e creare l'oggetto di questa classe passando gli argomenti.

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

0

È necessario aggiungere una risorsa di piè di pagina xml vuota a listView e rilevare se questo piè di pagina è visibile.

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

Quindi nel tuo listView scroll listView lo fai

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


0

Se imposti un tag su una vista dell'ultimo elemento della listview, in seguito potrai recuperare la vista con il tag, se la vista è nulla è perché la vista non è più caricata. Come questo:

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

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

    }
}

allora se hai bisogno di sapere se l'ultimo elemento è stato caricato

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

0

Janwilx72 ha ragione, ma min sdk è 21, quindi creo questo metodo :

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

per 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{}

Forse tardi, solo per i ritardatari。


0

Se stai usando un adattatore personalizzato con il tuo listview (la maggior parte delle persone lo fa!), Qui viene fornita una bella soluzione!

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

Il metodo getView dell'adattatore rileva quando l'elenco è stato fatto scorrere fino all'ultimo elemento. Aggiunge anche la correzione per le rare volte in cui una posizione precedente viene chiamata anche dopo che l'adattatore ha già eseguito il rendering dell'ultima vista.


0

L'ho fatto e lavora per me:

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

-5

Questo scorrerà l'elenco fino all'ultima voce.

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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.