¿Averiguar si ListView se desplaza hasta la parte inferior?

105

¿Puedo averiguar si mi ListView se desplaza hasta la parte inferior? Con eso quiero decir que el último elemento es completamente visible.

fhucho
fuente

Respuestas:

171

Editado :

Dado que he estado investigando este tema en particular en una de mis aplicaciones, puedo escribir una respuesta extendida para futuros lectores de esta pregunta.

Implemente un OnScrollListener, configure ListViewlos suyos onScrollListenery luego podrá manejar las cosas correctamente.

Por ejemplo:

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
fuente
26
Esto no detectará si el último elemento está completamente visible.
fhucho
1
Si tiene encabezados o pies de página en su vista de lista, también debe tenerlo en cuenta.
Martin Marconcini
5
@Wroclai Esto no detectará si el último elemento es completamente visible.
Gaurav Arora
Estaba buscando un código de FUNCIONAMIENTO similar. ¡¡Funcionó !! ¡¡Muchas gracias!!
Suraj Dubey
No quiero comenzar una nueva pregunta, pero ¿qué hago si listviewes mi stackFromBottom? Lo intenté, if (0 == firstVisibleItem){//listviewtop}pero me llaman repetidamente.
shadyinside
68

Respuesta tardía, pero si simplemente desea verificar si su ListView se desplaza completamente hacia abajo o no, sin crear un detector de eventos, puede usar esta declaración if:

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

}

Primero comprueba si la última posición posible está a la vista. Luego verifica si la parte inferior del último botón se alinea con la parte inferior de ListView. Puede hacer algo similar para saber si está en la parte superior:

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

}
Martijn
fuente
4
Gracias. Solo necesito cambiar ... getChildAt (yourListView.getCount () -1) ... a ... getChildAt (yourListView.getChildCount () -1) ...
OferR
@OferR No estoy seguro, pero creo que getChildCount()devuelve las vistas en el grupo de vistas, que con el reciclaje de vistas no es lo mismo que la cantidad de elementos en el adaptador. Sin embargo, dado que ListView desciende de AdapterView, puede usarlo getCount()directamente en ListView.
William T. Mallard
@ William T. Mallard Su primera oración es correcta, y esto es exactamente lo que queremos: La última vista que se muestra en el grupo. Esto es para verificar que sea completamente visible. (Considere un ListView con 20 líneas, pero solo se muestran las últimas 8. Queremos obtener la octava vista en el ViewGroup, no la vigésima que no existe)
OferR
tenga en cuenta que esta solución no funcionará si está actualizando rápidamente la vista de lista.
ardilla
19

La forma en que lo hice:

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
fuente
1
¡Es impresionante! Gracias
shariful islam
10

Algo en el sentido de:

if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
Chris Stewart
fuente
1
Esto no detectará si el último elemento está completamente visible.
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)
    }
}

Esto funcionó para mí.

Janwilx72
fuente
1
El método view.canScrollList es API 19+ solo desafortunadamente
ozmank
Funciona como un encanto
pavaniiitn
4

Esto puede 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");
                }
            }
señor perezoso
fuente
3

Fue bastante doloroso lidiar con el desplazamiento, detectar cuándo está terminado y, de hecho, está al final de la lista (no al final de la pantalla visible), y activa mi servicio solo una vez, para obtener datos de la web. Sin embargo, ahora funciona bien. El código es el siguiente para beneficio de cualquiera que se enfrente a la misma situación.

NOTA: Tuve que mover el código relacionado con mi adaptador a onViewCreated en lugar de onCreate y detectar el desplazamiento principalmente así:

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

Aquí RideListSimpleCursorAdapter.REACHED_THE_END es una variable adicional en mi SimpleCustomAdapter que se establece así:

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

Solo cuando se cumplen ambas condiciones, significa que estoy al final de la lista y que mi servicio se ejecutará solo una vez. Si no capto el REACHED_THE_END, incluso el desplazamiento hacia atrás activa el servicio nuevamente, siempre que el último elemento esté a la vista.

Zeeshan
fuente
Me deshice del resto porque me estaba causando problemas, pero como una buena respuesta general
Mike Baglio Jr.
3

canScrollVertically(int direction)funciona para todas las Vistas y parece hacer lo que pidió, con menos código que la mayoría de las otras respuestas. Ingrese un número positivo y, si el resultado es falso, estará en la parte inferior.

es decir:

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

Joel H
fuente
2

Para ampliar un poco una de las respuestas anteriores, esto es lo que tuve que hacer para que funcionara por completo. Parece haber alrededor de 6dp de relleno incorporado dentro de ListViews, y se estaba llamando a onScroll () cuando la lista estaba vacía. Esto maneja ambas cosas. Probablemente podría optimizarse un poco, pero está escrito más para mayor claridad.

Nota al margen: he probado varias técnicas diferentes de conversión de dp a píxeles, y esta dp2px () ha sido la mejor.

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
fuente
2

Todavía no puedo comentar porque no tengo suficiente reputación, pero en la respuesta de @Ali Imran y @Wroclai creo que falta algo. Con ese fragmento de código, una vez que actualice preLast, nunca volverá a ejecutar el registro. En mi problema específico, quiero ejecutar alguna operación cada vez que me desplazo hacia la parte inferior, pero una vez que preLast se actualiza a LastItem, esa operación nunca se vuelve a ejecutar.

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 ese "más" ahora puede ejecutar su código (Log, en este caso) cada vez que se desplaza hacia la parte inferior una vez más.

Teno González Dos Santos
fuente
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
fuente
1

Para que su lista llame cuando la lista llegue por última vez y si ocurre un error, esto no volverá a llamar a endoflistview . Este código también ayudará en este escenario.

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

donde LIST_UP_THRESHOLD_VALUE puede ser cualquier valor entero (he usado 5) donde su lista se desplaza hacia arriba y mientras regresa al final, esto llamará nuevamente al final de la vista de lista.

Vyshnavi
fuente
1

Encontré una forma muy agradable de cargar automáticamente el siguiente conjunto de páginas de una manera que no requiere la suya propia ScrollView(como lo requiere la respuesta aceptada).

En ParseQueryAdapter hay un método llamado getNextPageViewque está ahí para permitirle proporcionar su propia vista personalizada que aparece al final de la lista cuando hay más datos para cargar, por lo que solo se activará cuando haya llegado al final de su conjunto de páginas actual (es la vista "cargar más ..." por defecto). Este método solo se llama cuando hay más datos para cargar, por lo que es un excelente lugar para llamar.De loadNextPage(); esta manera, el adaptador hace todo el trabajo duro por usted para determinar cuándo se deben cargar nuevos datos y no se llamará en absoluto si tiene llegó al final del conjunto de datos.

public class YourAdapter extends ParseQueryAdapter<ParseObject> {

..

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

}

Luego, dentro de su actividad / fragmento, solo tiene que configurar el adaptador y los nuevos datos se actualizarán automáticamente para usted como por arte de magia.

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

Para detectar si el último elemento está completamente visible , puede agregar un cálculo simple en la parte inferior del último elemento visible de la vista 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;
    }
}
Será
fuente
1

Yo fui 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
    }
}
Goran Horia Mihail
fuente
1

En el método getView()(de una BaseAdapterclase derivada) se puede comprobar si la posición de la vista actual es igual a la lista de elementos en el Adapter. Si ese es el caso, significa que hemos llegado al final / final de la 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
    }
}
Ramón
fuente
0

Encuentro una mejor manera de detectar el final del desplazamiento de la vista de lista en la parte inferior, primero detecta el final del desplazamiento mediante esta
Implementación de onScrollListener para detectar el final del desplazamiento en 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 combine la respuesta 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
fuente
0

¡Muchas gracias a los carteles en stackoverflow! Combiné algunas ideas y creé un detector de clases para actividades y fragmentos (por lo que este código es más reutilizable, lo que hace que el código sea más rápido de escribir y mucho más limpio).

Todo lo que tienes que hacer cuando tienes mi clase es implementar la interfaz (y, por supuesto, crear un método para ella) que está declarado en mi clase y crear el objeto de esta clase pasando 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
fuente
0

Debe agregar un recurso de pie de página xml vacío a su listView y detectar si este pie de página es visible.

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

Luego, en su oyente de desplazamiento listView, haga esto

@
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
fuente
0

Si establece una etiqueta en una vista del último elemento de la vista de lista, más tarde puede recuperar la vista con la etiqueta, si la vista es nula es porque la vista ya no se carga. Me gusta esto:

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

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

    }
}

entonces, si necesita saber si se cargó el último artículo

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

Janwilx72 tiene razón, pero su min sdk es 21, así que creo 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{}

Tal vez tarde, solo para los que llegan tarde

xiaoyee
fuente
0

Si está utilizando un adaptador personalizado con su vista de lista (¡la mayoría de la gente lo hace!) ¡Aquí se ofrece una hermosa solución!

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

El método getView del adaptador detecta cuando la lista se ha desplazado hasta el último elemento. También agrega corrección para las raras ocasiones en que se llama a una posición anterior incluso después de que el adaptador ya haya renderizado la última vista.

gbenroscience
fuente
0

Hice eso y funciona para mí:

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
fuente
-5

Esto se desplazará hacia abajo en su lista hasta la ú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
fuente