Risposte:
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;
}
}
}
}
listviewè stackFromBottom? Ho provato, if (0 == firstVisibleItem){//listviewtop}ma viene chiamato ripetutamente.
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
}
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.
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) {
}
});
Qualcosa sull'effetto di:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
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.
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");
}
}
È 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.
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
}
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());
}
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.
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
}
}
}
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.
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);
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;
}
}
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
}
}
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
}
}
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");
}
}
}
};
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;
}
}
}
È 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);
}
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
}
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。
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.
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");
}
}
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);