Liste sans fin Android

232

Comment puis-je créer une liste où lorsque vous atteignez la fin de la liste, je suis averti afin que je puisse charger plus d'articles?

Isaac Waller
la source
9
Je recommande EndlessAdapter de CommonWare: github.com/commonsguy/cwac-endless . Je l'ai trouvé à partir de cette réponse à une autre question: stackoverflow.com/questions/4667064/… .
Tyler Collier
J'ai besoin de la même chose ... peut-on aider? .. stackoverflow.com/questions/28122304/…

Réponses:

269

Une solution consiste à implémenter un OnScrollListeneret à apporter des modifications (comme l'ajout d'éléments, etc.) ListAdapterà un état pratique dans sa onScrollméthode.

Ce qui suit ListActivitymontre une liste d'entiers, commençant par 40, ajoutant des éléments lorsque l'utilisateur fait défiler jusqu'à la fin de la liste.

public class Test extends ListActivity implements OnScrollListener {

    Aleph0 adapter = new Aleph0();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(adapter); 
        getListView().setOnScrollListener(this);
    }

    public void onScroll(AbsListView view,
        int firstVisible, int visibleCount, int totalCount) {

        boolean loadMore = /* maybe add a padding */
            firstVisible + visibleCount >= totalCount;

        if(loadMore) {
            adapter.count += visibleCount; // or any other amount
            adapter.notifyDataSetChanged();
        }
    }

    public void onScrollStateChanged(AbsListView v, int s) { }    

    class Aleph0 extends BaseAdapter {
        int count = 40; /* starting amount */

        public int getCount() { return count; }
        public Object getItem(int pos) { return pos; }
        public long getItemId(int pos) { return pos; }

        public View getView(int pos, View v, ViewGroup p) {
                TextView view = new TextView(Test.this);
                view.setText("entry " + pos);
                return view;
        }
    }
}

Vous devez évidemment utiliser des threads séparés pour les actions de longue durée (comme le chargement de données Web) et vous pouvez indiquer la progression dans le dernier élément de la liste (comme le font les applications de marché ou gmail).

Josef Pfleger
la source
3
hein, cela ne déclencherait-il pas l'augmentation de la taille de l'adaptateur si vous arrêtiez de faire défiler au milieu de l'écran? il ne devrait charger les 10 suivants qu'en atteignant réellement le bas de la liste.
Matthias
9
Je remarque beaucoup d'exemples comme celui-ci en utilisant BaseAdapter. Je voulais mentionner que si vous utilisez une base de données, essayez d'utiliser un SimpleCursorAdapter. Lorsque vous devez ajouter à la liste, mettez à jour votre base de données, obtenez un nouveau curseur et utilisez SimpleCursorAdapter # changeCursor avec notifyDataSetChanged. Aucun sous-classement requis, et il fonctionne mieux qu'un BaseAdapter (encore une fois, uniquement si vous utilisez une base de données).
brack
1
Après avoir appelé notifyDataSetChanged (), il ira en haut de la liste, comment puis-je l'empêcher?
tirage du
2
Une note - J'ai utilisé cette approche, mais je devais ajouter une vérification lorsque visibleCount est 0. Pour chaque liste, j'obtiens un rappel à onScroll où firstVisible, visibleCount et totalCount sont tous à 0, ce qui répond techniquement au conditionnel, mais n'est pas quand je veux charger plus.
Eric Mill
5
Un autre problème avec ce code est que la condition firstVisible + visibleCount >= totalCountest remplie plusieurs fois avec plusieurs appels à l'écouteur. Si la fonction de chargement supplémentaire est une demande Web (très probablement), ajoutez une autre vérification pour savoir si une demande est en cours ou non. D'un autre côté, vérifiez si le totalCount n'est pas égal au nombre total précédent, car nous n'avons pas besoin de plusieurs demandes pour le même nombre d'éléments dans la liste.
Subin Sebastian
94

Je voulais juste apporter une solution que j'ai utilisée pour mon application.

Il est également basé sur l' OnScrollListenerinterface, mais j'ai trouvé qu'il avait de bien meilleures performances de défilement sur les appareils bas de gamme, car aucun des calculs de nombre total / visible n'est effectué pendant les opérations de défilement.

  1. Laissez votre ListFragmentou ListActivitymettre en œuvreOnScrollListener
  2. Ajoutez les méthodes suivantes à cette classe:

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        //leave this empty
    }
    
    @Override
    public void onScrollStateChanged(AbsListView listView, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            if (listView.getLastVisiblePosition() >= listView.getCount() - 1 - threshold) {
                currentPage++;
                //load more list items:
                loadElements(currentPage);
            }
        }
    }

    currentPageest la page de votre source de données qui doit être ajoutée à votre liste, et thresholdest le nombre d'éléments de liste (comptés à partir de la fin) qui devraient, s'ils sont visibles, déclencher le processus de chargement. Si vous définissez thresholdà 0, par exemple, l'utilisateur doit faire défiler jusqu'à la fin de la liste afin de charger plus d' articles.

  3. (facultatif) Comme vous pouvez le voir, la «vérification de chargement supplémentaire» n'est appelée que lorsque l'utilisateur arrête de faire défiler. Pour améliorer la convivialité, vous pouvez gonfler et ajouter un indicateur de chargement à la fin de la liste via listView.addFooterView(yourFooterView). Un exemple pour une telle vue de pied de page:

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/footer_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp" >
    
        <ProgressBar
            android:id="@+id/progressBar1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_gravity="center_vertical" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/progressBar1"
            android:padding="5dp"
            android:text="@string/loading_text" />
    
    </RelativeLayout>
  4. (facultatif) Enfin, supprimez cet indicateur de chargement en appelant listView.removeFooterView(yourFooterView)s'il n'y a plus d'éléments ou de pages.

saschoar
la source
J'ai essayé cela mais cela ne fonctionne pas pour ListFragment. Comment peut-il être utilisé dans ListFragment.
zeeshan
Eh bien, je l'utilise ListFragmentégalement sans aucun problème - suivez simplement chacune des étapes ci-dessus et tout ira bien;) Ou pourriez-vous fournir des messages d'erreur?
saschoar
Ce que vous pourriez avoir manqué est expliqué dans cette réponse SO: stackoverflow.com/a/6357957/1478093 -getListView().setOnScrollListener(this)
TBieniek
7
Remarque: l'ajout et la suppression du pied de page dans listview est problématique (le pied de page n'apparaît pas si setAdapter est appelé avant d'ajouter le pied de page), j'ai fini par modifier directement la visibilité de la vue de pied de page sans la supprimer.
dvd
Que faut-il ajouter dans les loadElements (currentPage) ??? Je veux dire est-ce que j'appelle ma AsyncTask ou un thread?
android_developer
20

Vous pouvez détecter la fin de la liste à l'aide de onScrollListener , le code de travail est présenté ci-dessous:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (view.getAdapter() != null && ((firstVisibleItem + visibleItemCount) >= totalItemCount) && totalItemCount != mPrevTotalItemCount) {
        Log.v(TAG, "onListEnd, extending list");
        mPrevTotalItemCount = totalItemCount;
        mAdapter.addMoreData();
    }
}

Une autre façon de le faire (à l'intérieur de l'adaptateur) est la suivante:

    public View getView(int pos, View v, ViewGroup p) {
            if(pos==getCount()-1){
                addMoreData(); //should be asynctask or thread
            }
            return view;
    }

Sachez que cette méthode sera appelée plusieurs fois, vous devez donc ajouter une autre condition pour bloquer plusieurs appels de addMoreData() .

Lorsque vous ajoutez tous les éléments à la liste, veuillez appeler notifyDataSetChanged () dans votre adaptateur pour mettre à jour la vue (elle doit être exécutée sur le thread d'interface utilisateur - runOnUiThread )

Dariusz Bacinski
la source
7
Il est inapproprié de faire autre chose que de renvoyer une vue getView. Par exemple, vous n'êtes pas assuré que posl'utilisateur le voit. Il pourrait simplement s'agir de mesures ListView.
Thomas Ahle
Je veux charger plus d'articles après le défilement de 10 articles. mais je remarque que le chargement commence avant que le 10ème élément soit visible signifie sur le 9ème élément. Alors, comment arrêter cela?
Atul Bhardwaj
4

Chez Ognyan Bankov GitHubj'ai trouvé une solution simple et fonctionnelle!

Il utilise le Volley HTTP libraryqui rend la mise en réseau pour les applications Android plus facile et, surtout, plus rapide. Volley est disponible via le référentiel AOSP ouvert.

Le code donné montre:

  1. ListView qui est rempli par des requêtes paginées HTTP.
  2. Utilisation de NetworkImageView.
  3. Pagination ListView "sans fin" avec lecture anticipée.

Pour une cohérence future, j'ai bifurqué le repo de Bankov .

oikonomopo
la source
2

Voici une solution qui facilite également l'affichage d'une vue de chargement à la fin de ListView pendant le chargement.

Vous pouvez voir les cours ici:

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java - Aide à rendre possible l'utilisation du fonctionnalités sans s'étendre à partir de SimpleListViewWithLoadingIndicator.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java - Écouteur qui démarre le chargement des données lorsque l'utilisateur est sur le point d'atteindre le bas du ListView.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java - The EndlessListView. Vous pouvez utiliser cette classe directement ou l'étendre.

Fernando Camargo
la source
1

Peut être un peu en retard mais la solution suivante s'est avérée très utile dans mon cas. D'une manière tout ce que vous devez faire est d'ajouter à votre ListView Footeret de créer pour celaaddOnLayoutChangeListener .

http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)

Par exemple:

ListView listView1 = (ListView) v.findViewById(R.id.dialogsList); // Your listView
View loadMoreView = getActivity().getLayoutInflater().inflate(R.layout.list_load_more, null); // Getting your layout of FooterView, which will always be at the bottom of your listview. E.g. you may place on it the ProgressBar or leave it empty-layout.
listView1.addFooterView(loadMoreView); // Adding your View to your listview 

...

loadMoreView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
         Log.d("Hey!", "Your list has reached bottom");
    }
});

Cet événement se déclenche une fois lorsqu'un pied de page devient visible et fonctionne comme un charme.

tehcpu
la source
0

La clé de ce problème est de détecter l'événement de chargement supplémentaire, de démarrer une demande asynchrone de données, puis de mettre à jour la liste. Un adaptateur avec indicateur de chargement et autres décorateurs est également nécessaire. En fait, le problème est très compliqué dans certains cas d'angle. Une simple OnScrollListenermise en œuvre ne suffit pas, car parfois les éléments ne remplissent pas l'écran.

J'ai écrit un package personnel qui prend en charge une liste sans fin RecyclerViewet fournit également une implémentation de chargeur asynchrone AutoPagerFragmentqui facilite très facilement l'obtention de données à partir d'une source de plusieurs pages. Il peut charger n'importe quelle page de votre choix dans un RecyclerViewévénement personnalisé, pas seulement la page suivante.

Voici l'adresse: https://github.com/SphiaTower/AutoPagerRecyclerManager

ProtossShuttle
la source
Le lien est rompu.
DSlomer64
0

Jusqu'à présent, la meilleure solution que j'ai vue est dans la bibliothèque FastAdapter pour les vues de recycleur. Il a un EndlessRecyclerOnScrollListener.

Voici un exemple d'utilisation: EndlessScrollListActivity

Une fois que je l'ai utilisé pour une liste de défilement sans fin, j'ai réalisé que la configuration est très robuste. Je le recommanderais sans hésiter.

Shubham Chaudhary
la source
0

J'ai travaillé dans une autre solution très similaire à celle-ci, mais, j'utilise un footerViewpour donner la possibilité à l'utilisateur de télécharger plus d'éléments en cliquant sur le footerView, j'utilise un "menu" qui est affiché au-dessus du ListViewet en bas du vue parent, ce "menu" masque le bas du ListView, donc, lorsque le listViewdéfilement disparaît et que l'état de défilement est inactif, le menu réapparaît, mais lorsque l'utilisateur défile jusqu'à la fin du listView, je "demande" à savoir si footerViews'affiche dans ce cas, le menu n'apparaît pas et l'utilisateur peut voir footerViewpour charger plus de contenu. Voici le code:

Cordialement.

listView.setOnScrollListener(new OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub
        if(scrollState == SCROLL_STATE_IDLE) {
            if(footerView.isShown()) {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.VISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            }
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {

    }
});
pabloverd
la source
0

Je sais que c'est une vieille question et le monde Android est principalement passé à RecyclerViews, mais pour toute personne intéressée, cette bibliothèque peut être très intéressante.

Il utilise le BaseAdapter utilisé avec le ListView pour détecter quand la liste a été défilée jusqu'au dernier élément ou lorsqu'elle est éloignée du dernier élément.

Il est livré avec un exemple de projet (à peine 100 lignes de code d'activité) qui peut être utilisé pour comprendre rapidement comment cela fonctionne.

Utilisation simple:

class Boy{

private String name;
private double height;
private int age;
//Other code

}

Un adaptateur pour contenir des objets Boy ressemblerait à ceci:


public class BoysAdapter extends EndlessAdapter<Boy>{




        ViewHolder holder = null;


        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent
                    .getContext());

            holder = new ViewHolder();

            convertView = inflater.inflate(
                    R.layout.list_cell, parent, false);


            holder.nameView = convertView.findViewById(R.id.cell);

            // minimize the default image.
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Boy boy = getItem(position);

        try {
            holder.nameView.setText(boy.getName());

            ///Other data rendering codes.

        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.getView(position,convertView,parent);

}

Remarquez comment la méthode getView de BoysAdapter renvoie un appel à la méthode de la superclasse EndlessAdapter getView. C'est 100% essentiel.

Maintenant, pour créer l'adaptateur, procédez comme suit:

   adapter = new ModelAdapter() {
            @Override
            public void onScrollToBottom(int bottomIndex, boolean moreItemsCouldBeAvailable) {

                if (moreItemsCouldBeAvailable) { 
                    makeYourServerCallForMoreItems();
                } else {
                    if (loadMore.getVisibility() != View.VISIBLE) {
                        loadMore.setVisibility(View.VISIBLE);
                    }
                }
            }

            @Override
            public void onScrollAwayFromBottom(int currentIndex) { 
                loadMore.setVisibility(View.GONE);
            }

            @Override
            public void onFinishedLoading(boolean moreItemsReceived) { 
                if (!moreItemsReceived) {
                    loadMore.setVisibility(View.VISIBLE);
                }
            }
        };

le loadMore élément est un bouton ou un autre élément d'interface utilisateur qui peut être cliqué pour extraire plus de données de l'url. Lorsqu'il est placé comme décrit dans le code, l'adaptateur sait exactement quand afficher ce bouton et quand le désactiver. Créez simplement le bouton dans votre xml et placez-le comme indiqué dans le code adaptateur ci-dessus.

Prendre plaisir.

gbenroscience
la source