web-dev-qa-db-fra.com

Implémentation de onScrollListener pour détecter la fin du défilement dans un ListView

J'ai une ListView affichant des éléments. J'aimerais effectuer quelques opérations sur les éléments actuellement affichés dans la partie visible de ListView, en fonction du défilement de ListView; j'ai donc pensé implémenter la OnScrollListener de la ListView. Conformément à la référence de l'API Android, la méthode onScroll "sera appelée une fois le parchemin terminé". Cela me semble juste ce dont j'avais besoin, car une fois le parchemin terminé, j'effectue mes actions sur la variable ListView (la méthode onScroll renvoie l'index du premier élément affiché et le nombre d'éléments affichés). 

Mais une fois implémenté, je vois dans la variable LogCat que la méthode onScroll n'est pas simplement déclenchée une fois le défilement terminé, mais pour chaque nouvel élément entrant dans la vue d'affichage, du début à la fin du défilement. Ce n'est pas le comportement que j'attends ni dont j'ai besoin. L'autre méthode de l'écouteur (onScrollStateChanged) ne fournit pas d'informations sur les éléments actuellement affichés dans la variable ListView.

Alors, est-ce que quelqu'un sait comment utiliser ce couple de méthodes pour détecter la fin du parchemin et obtenir les informations sur les éléments affichés? Le désalignement entre la référence de l'API et le comportement réel de la méthode m'a un peu dérouté ... Merci d'avance.

P.S .: J'ai déjà vu des sujets similaires, mais rien ne m'aide à comprendre comment tout ça fonctionne ..!

42
e-cal

Au final, j'ai trouvé une solution moins élégante, mais qui a fonctionné pour moi. ayant découvert que la méthode onScroll est appelée à chaque étape du défilement au lieu d'être appelée simplement à la fin du défilement, et que onScrollStateChanged n'est en fait appelée que lorsque le défilement est terminé, je fais comme ceci:

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

Pratiquement, à chaque défilement de ListView, j'enregistre les données relatives au premier élément visible et au nombre d'éléments visibles (méthode onScroll); Lorsque l'état de défilement change (onScrollStateChanged), je sauvegarde l'état et j'appelle une autre méthode qui permet de comprendre s'il y a eu un défilement et s'il est terminé. De cette façon, j'ai aussi les données sur les éléments visibles dont j'ai besoin… peut-être pas propres, mais ça marche!

Cordialement

54
e-cal
@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
  }
}
13
Duna
@Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {

            if((firstVisibleItem + visibleItemCount) ==  totalItemCount)
            {
                Log.i("Info", "Scroll Bottom" );
            }
        }});
9
trytrysee

Obtenir ce comportement est délicat, et il m'a fallu un certain temps pour le perfectionner. Le problème, c’est que les auditeurs de défilement ne sont pas vraiment suffisants pour détecter un «arrêt de défilement» (y compris les boutons directionnels/trackball), à ma connaissance. J'ai fini par faire une combinaison de choses qui fonctionne comme je le souhaite. 

La meilleure façon de le faire était d’allonger ListView et d’écraser quelques méthodes:

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

Notez que vous devez également lier la customScrollListener dans vos constructeurs. Je pense que cette implémentation est agréable, car elle ne déclenchera pas immédiatement "l'événement", elle attendra un peu jusqu'à ce que le défilement soit complètement arrêté. 

6
dmon

Analyser les réponses précédentes, trouvé sa propre:

Dans onViewCreated, onCreate ou n'importe où vous récupérez votre listview: 

ListView list = (ListView) view.findViewById(R.id.[your id]);
    list.setOnScrollListener(this);

Puis complétez les méthodes d'écoute:

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

Semble faire ce qu’il faut: détecter la fin de liste sans tonnes de code et sans modifications de l’adaptateur.

3
Nikola-L_F

Essaye celui-là... 

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

Juste une représentation plus utile de la réponse de e-cal

Implémentation de l'auditeur:

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

Utilisation:

    listView.setOnScrollListener(new OnScrollFinishListener() {
         @Override
         protected void onScrollFinished() {
             // do whatever you need here!
         }
    });
3
Oleksandr

J'ai rencontré beaucoup de problèmes en essayant de déterminer si la liste se trouvait en bas de OnScrollListener, la meilleure solution que j'ai trouvée est de vérifier À partir de la méthode getView de l'adaptateur si je montre actuellement la dernière ligne:

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

là, je peux facilement créer une interface pour informer le fragment/l'activité que j'ai atteint le bas.

à votre santé.

0
Gal Rom

Il existe encore une solution simple. Mettez un auditeur dans l'adaptateur de listview et dans getview vérifiez si (position == totalItemCount du nombre d'éléments dans un tableau à afficher), si oui, j'appelle mon écouteur depuis l'adaptateur et dans l'écouteur

0
Ajith Memana