web-dev-qa-db-fra.com

Savoir si ListView a défilé jusqu'en bas?

Puis-je savoir si ma liste est défilée vers le bas? J'entends par là que le dernier élément est entièrement visible.

100
fhucho

Edité :

Depuis que j'ai étudié ce sujet particulier dans l'une de mes applications, je peux écrire une réponse plus complète pour les futurs lecteurs de cette question.

Implémentez une OnScrollListener, définissez votre ListView 's onScrollListener et vous devriez alors pouvoir gérer les choses correctement. 

Par exemple:

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

Réponse tardive, mais si vous souhaitez simplement vérifier si votre ListView est défilée ou non, sans créer d'écouteur d'événement, vous pouvez utiliser cette instruction if:

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

}

Il vérifie d'abord si la dernière position possible est visible. Ensuite, il vérifie si le bas du dernier bouton s'aligne avec le bas de ListView. Vous pouvez faire quelque chose de similaire pour savoir si c'est tout en haut:

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

}
68
Martijn

La façon dont je l'ai fait:

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

    }
});
15
t-gao

Quelque chose à l'effet de:

if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
10
Chris Stewart
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)
    }
}

Cela a fonctionné pour moi.

6
Janwilx72

Cela peut être  

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

canScrollVertically(int direction) fonctionne pour toutes les vues et semble faire ce que vous avez demandé avec moins de code que la plupart des autres réponses. Branchez un nombre positif et si le résultat est faux, vous êtes au bas de la liste.

c'est à dire:

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

3
Joel H

Il était assez pénible de gérer le défilement, de détecter son achèvement et de le placer en bas de la liste (et non en bas de l'écran visible), et de déclencher mon service une seule fois, pour extraire des données du Web. Cependant, cela fonctionne bien maintenant. Le code est le suivant pour le bénéfice de toute personne confrontée à la même situation.

REMARQUE: je devais déplacer le code associé à mon adaptateur dans onViewCreated au lieu de onCreate et détecter le défilement principalement comme ceci:

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

Ici RideListSimpleCursorAdapter.REACHED_THE_END est une variable supplémentaire dans mon SimpleCustomAdapter qui est définie comme suit:

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

Ce n'est que lorsque ces deux conditions sont réunies que cela signifie que je suis effectivement au bas de la liste et que mon service ne fonctionnera qu'une fois. Si je ne récupère pas REACHED_THE_END, même le défilement arrière déclenche à nouveau le service, tant que le dernier élément est affiché.

3
zeeshan

Je ne peux pas encore commenter car je n'ai pas assez de réputation, mais dans la réponse de @ALi Imran et @Wroclai, je pense qu'il manque quelque chose. Avec ce morceau de code, une fois que vous mettez à jour preLast, le journal ne sera plus jamais exécuté . Dans mon problème spécifique, je souhaite exécuter une opération à chaque fois que je fais défiler l'écran vers le bas, mais l'opération n'est jamais exécutée à nouveau. 

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

}

Avec ce paramètre "else", vous pouvez maintenant exécuter votre code (Log, dans ce cas) à chaque fois que vous faites défiler de nouveau l'écran.

Pour développer un peu plus l’une des réponses ci-dessus, c’est ce que j’avais à faire pour que cela fonctionne complètement. Il semble y avoir environ 6dp de remplissage intégré dans ListViews, et onScroll () était appelé lorsque la liste était vide. Cela gère ces deux choses. Il pourrait probablement être optimisé un peu, mais est écrit plus pour plus de clarté.

Note latérale: J'ai essayé plusieurs techniques de conversion de dp en pixel, et celle-ci, dp2px (), est la meilleure.

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
CSX321

J'y suis allé avec:

@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
Goran Horia Mihail

Pour détecter si le dernier élément est entièrement visible , vous pouvez simplement ajouter un calcul sur le dernier élément visible de la vue par 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
Will

Pour que votre liste appelle la dernière liste et si une erreur se produit, la fin de la liste n’appellera plus . Ce code aidera également ce scénario. 

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

où LIST_UP_THRESHOLD_VALUE peut être n’importe quelle valeur entière (j’en ai utilisée 5) où votre liste défile et en revenant à la fin, cela appellera à nouveau la vue de fin de liste.

1
Vyshnavi

Dans la méthode getView() (d'une classe dérivée de BaseAdapter), on peut vérifier si la position de la vue actuelle est égale à la liste des éléments de la Adapter. Si tel est le cas, cela signifie que nous avons atteint la fin/le bas de la liste:

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

J'ai trouvé un moyen très agréable de charger automatiquement le jeu de pages suivant d'une manière qui n'exige pas votre propre ScrollView (comme l'exige la réponse acceptée).

Sur ParseQueryAdapter il existe une méthode appelée getNextPageView qui vous permet de fournir votre propre vue personnalisée qui apparaît à la fin de la liste lorsqu'il y a plus de données à charger. Elle ne se déclenchera que lorsque vous avez atteint la fin de votre ensemble de pages actuel (la vue "chargez plus .." par défaut). Cette méthode est seulement appelée quand il y a plus de données à charger, donc c'est un endroit génial pour appeler loadNextPage();. Ainsi, l'adaptateur fait tout le travail difficile à votre place pour déterminer quand de nouvelles données doivent être chargées et ne seront pas appelées. du tout si vous avez atteint la fin du jeu de données.

public class YourAdapter extends ParseQueryAdapter<ParseObject> {

..

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

}

Ensuite, dans votre activité/fragment, vous devez simplement configurer l’adaptateur et les nouvelles données seront automatiquement mises à jour pour vous, comme par magie.

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

Un grand merci aux affiches de stackoverflow! J'ai combiné certaines idées et créé un auditeur de classe pour les activités et les fragments (ce code est donc plus réutilisable, ce qui le rend plus rapide à écrire et plus propre).

Tout ce que vous avez à faire quand vous avez ma classe est d'implémenter une interface (et bien sûr une méthode pour la créer) déclarée dans ma classe et créer un objet de cette classe en passant des arguments.

/**
* 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
Janusz Hain

Janwilx72 a raison, mais son min sdk a 21 ans, je crée donc cette méthode:

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

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

Peut-être en retard, juste pour les retardataires

0
xiaoyee
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
            }
        }
    }
0
John Ruban Singh

Vous devez ajouter une ressource de pied de page XML vide à votre listView et détecter si ce pied de page est 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;
    }

Ensuite, dans votre listView scroll listener, vous faites ceci 

@
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
Joseph Mhanna

Si vous définissez une balise sur une vue du dernier élément de la liste, vous pourrez ensuite récupérer la vue avec la balise. Si la vue est nulle, c'est que la vue n'est plus chargée. Comme ça:

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

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

    }
}

alors si vous avez besoin de savoir si le dernier élément est chargé

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

Je trouve un meilleur moyen de détecter le listview scroll end the bottom, d’abord la détection de scoll end by this 
Implémentation de onScrollListener pour détecter la fin du défilement dans 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! ***/
    }
}

enfin combiner la réponse 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");
                }


            }
        }

    };
0
HenryChuang

Si vous utilisez un adaptateur personnalisé avec votre listview (la plupart des gens le font!), Une belle solution vous est proposée ici!

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

La méthode getView de l'adaptateur détecte le moment où la liste a été déplacée jusqu'au dernier élément. Il ajoute également une correction pour les rares moments où une position antérieure est appelée même après que l'adaptateur a déjà rendu la dernière vue.

0
gbenroscience