android - Implementación de onScrollListener para detectar el final del desplazamiento en un ListView
(9)
Tengo un ListView
muestra algunos elementos. Me gustaría realizar alguna operación en los elementos que se muestran actualmente en la parte visible de ListView
, según cómo se haya desplazado ListView
; así que pensé implementar el OnScrollListener
de ListView
. De acuerdo con la referencia api de Android, el método onScroll "se llamará después de que el desplazamiento se haya completado". Esto me parece correcto, ya que una vez que el rollo se ha completado, realizo mis acciones en el ListView
(el método onScroll devuelve el índice del primer elemento que se muestra y el número de elementos mostrados).
Pero una vez implementado, veo en el LogCat
que el método onScroll no solo se dispara una vez que se completó el desplazamiento, sino que se dispara por cada elemento nuevo que entre en la vista de visualización, desde el principio hasta el final del desplazamiento. Este no es el comportamiento que espero ni necesito. El otro método del oyente (onScrollStateChanged), en cambio, no proporciona información sobre los elementos que se muestran actualmente en ListView
.
Entonces, ¿alguien sabe cómo usar este par de métodos para detectar el final del desplazamiento y obtener la información sobre los elementos mostrados? La desalineación entre la referencia de la API y el comportamiento real del método me confundió un poco. Gracias por adelantado.
PD: He visto algunos temas similares, ¡pero nada me ayuda a entender cómo funciona todo!
Al final he llegado a una solución no tan elegante, pero eso funcionó para mí; habiendo descubierto que se llama al método onScroll para cada paso del desplazamiento en lugar de simplemente llamarse en el extremo del desplazamiento, y que onScrollStateChanged se está llamando solo cuando se completa el desplazamiento, hago algo como esto:
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! ***/
}
}
Prácticamente, cada vez que se desplaza ListView, guardo los datos sobre el primer elemento visible y el recuento de artículos visibles (métodoScroll); cuando el estado del desplazamiento cambia (onScrollStateChanged) guardo el estado y llamo a otro método que realmente entiende si ha habido un desplazamiento y si está completo. De esta manera, también tengo los datos sobre los elementos visibles que necesito. Quizás no esté limpio pero funciona!
Saludos
Analizando las respuestas anteriores, encontró las suyas:
En onViewCreated, onCreate o donde agarre su lista de lista:
ListView list = (ListView) view.findViewById(R.id.[your id]);
list.setOnScrollListener(this);
Luego complete los métodos de escucha:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// yes, just leave it empty
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.isScrollCompleted(view, scrollState);
}
private void isScrollCompleted(AbsListView view, int scrollState) {
if (!view.canScrollVertically(1) && this.currentScrollState == SCROLL_STATE_IDLE) {
// do work
}
}
Parece que hace lo que debería: detectar el final de la lista sin toneladas de código y modificaciones del adaptador.
Experimenté muchos problemas al tratar de averiguar si la lista está en la parte inferior del OnScrollListener, la mejor solución que encontré es verificar desde el método getView del adaptador si estoy mostrando la última fila:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ActivityView view;
if (convertView==null)
view = (ActivityView)inflater.inflate(R.layout.activity_item, null);
else
view = (ActivityView)convertView;
view.showRow(activitiesList.get(position),position,controller,this);
if(position == activitiesList.size() - 1)
{
Log.i("BOTTOM","reached");
}
return view;
}
allí puedo crear fácilmente una interfaz para notificar al Fragmento / Actividad que llegué al final.
aclamaciones.
Para bajar este comportamiento es complicado, y me tomó bastante tiempo perfeccionarlo. El problema es que los oyentes de desplazamiento por sí mismos no son en realidad suficientes para detectar una "parada de desplazamiento" (incluidos los botones direccionales / trackball), por lo que pude ver. Terminé haciendo una combinación de cosas que funciona bien como lo espero.
La mejor forma en que pensé que podría hacerlo fue extender ListView y anular algunos métodos:
....
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
startWait();
}
return super.onKeyUp(keyCode, event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
stopWait();
}
if (event.getAction() == MotionEvent.ACTION_UP ||
event.getAction() == MotionEvent.ACTION_CANCEL) {
startWait();
}
return super.onTouchEvent(event);
}
private OnScrollListener customScrollListener = new OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
stopWait();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
startWait();
} else {
stopWait();
}
}
};
//All this waiting could be improved, but that''s the idea
private Thread waitThread = null;
private int waitCount = Integer.MIN_VALUE;
public void stopWait() {
waitCount = Integer.MIN_VALUE;
}
public synchronized void startWait() {
waitCount = 0;
if (waitThread != null) {
return;
}
waitThread = new Thread(new Runnable() {
@Override
public void run() {
try {
for (; waitCount.get() < 10; waitCount++) {
Thread.sleep(50);
}
//Kick it back to the UI thread.
view.post(theRunnableWithYourOnScrollStopCode); // HERE IS WHERE YOU DO WHATEVER YOU WANTED TO DO ON STOP
} catch (InterruptedException e) {
} finally {
waitThread = null;
}
}
});
waitThread.start();
}
Tenga en cuenta que también debe vincular customScrollListener
en sus constructores. Esta implementación es agradable, creo, porque no activará inmediatamente el "evento", esperará un poco hasta que haya dejado de desplazarse por completo.
Prueba este ...
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
int position = firstVisibleItem+visibleItemCount;
int limit = totalItemCount;
// Check if bottom has been reached
if (position >= limit && totalItemCount > 0) {
//scroll end reached
//Write your code here
}
}
Todavía hay una solución más simple. Ponga un oyente en el Adaptador de la vista de lista y en el getview compruebe si (posición == totalItemCount de los elementos en la matriz se mostrará), si es así, llamo a mi oyente desde el adaptador y en el Oyente Ejecute su operación sea lo que sea
Una representación más útil de la respuesta de e-cal
Implementación del oyente:
public abstract class OnScrollFinishListener implements AbsListView.OnScrollListener {
int mCurrentScrollState;
int mCurrentVisibleItemCount;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
if (isScrollCompleted()) {
onScrollFinished();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mCurrentVisibleItemCount = visibleItemCount;
}
private boolean isScrollCompleted() {
return mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE;
}
protected abstract void onScrollFinished();
}
Uso:
listView.setOnScrollListener(new OnScrollFinishListener() {
@Override
protected void onScrollFinished() {
// do whatever you need here!
}
});
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if((firstVisibleItem + visibleItemCount) == totalItemCount)
{
Log.i("Info", "Scroll Bottom" );
}
}});
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
&& list.getChildAt(list.getChildCount() - 1).getBottom() <= list.getHeight()) {
//scroll end reached
//Write your code here
}
}