Comment mettre en œuvre une liste sans fin avec RecyclerView?

346

Je voudrais changer ListViewpour RecyclerView. Je veux utiliser onScrollleOnScrollListener dans RecyclerViewpour déterminer si un utilisateur défilée à la fin de la liste.

Comment savoir si un utilisateur défile jusqu'à la fin de la liste afin que je puisse récupérer de nouvelles données à partir d'un service REST?

erdna
la source
S'il vous plaît vérifier ce lien Il vous aidera .. stackoverflow.com/questions/26293461/...
Samsad
32
Veuillez lire attentivement la question! Je sais comment faire cela avec un ListView mais PAS comment l'implémenter avec un RecycleView .
erdna
comment implémenter loadmore onscrolllistener dans android.data est chargé mais mis à jour sur les données existantes s'il vous plaît aidez-moi
Harsha
2
Vous pouvez utiliser cette bibliothèque: github.com/rockerhieu/rv-adapter-endless . Il est basé sur l'idée de cwac-endlessfor ListView.
Hieu Rocker

Réponses:

422

Merci à @Kushal et c'est ainsi que je l'ai implémenté

private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (dy > 0) { //check for scroll down
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading) {
                if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                    loading = false;
                    Log.v("...", "Last Item Wow !");
                    // Do pagination.. i.e. fetch new data
                }
            }
        }
    }
});

N'oubliez pas d'ajouter

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Abdulaziz Noor
la source
11
Que puis-je faire pourStaggeredGridLayoutManager
Pratik Butani
16
Qu'en est-ilmLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1
Laaptu
46
Pour tous ceux qui findFirstVisibleItemPosition()n'ont pas résolu, vous devriez changer RecyclerView.LayoutManagerpourLinearLayoutManager
Amr Mohammed
27
où est la condition pour loading = truerefaire?
Bhargav
11
N'oubliez pas d'ajouter loading = trueaprès la //Do paginationpartie. sinon ce code ne s'exécutera qu'une seule fois et vous ne pourrez pas charger plus d'élément.
Shaishav Jogani
165

Faites ces variables.

private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;

Activez le défilement pour afficher le recycleur.

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading && (totalItemCount - visibleItemCount) 
            <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached

            Log.i("Yaeye!", "end called");

            // Do something

            loading = true;
        }
    }
});

Remarque: assurez-vous que vous utilisez LinearLayoutManagercomme gestionnaire de mise en page pour RecyclerView.

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

et pour une grille

GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);

Amusez-vous avec vos parchemins sans fin !! ^. ^

Mise à jour: mRecyclerView. setOnScrollListener () est déconseillé, remplacez-le par mRecyclerView.addOnScrollListener()et l'avertissement disparaîtra! Vous pouvez lire plus de cette question SO .

Depuis Android prend désormais officiellement en charge Kotlin, voici une mise à jour pour le même -

Faire OnScrollListener

class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
    var previousTotal = 0
    var loading = true
    val visibleThreshold = 10
    var firstVisibleItem = 0
    var visibleItemCount = 0
    var totalItemCount = 0

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        visibleItemCount = recyclerView.childCount
        totalItemCount = layoutManager.itemCount
        firstVisibleItem = layoutManager.findFirstVisibleItemPosition()

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false
                previousTotal = totalItemCount
            }
        }

        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            val initialSize = dataList.size
            updateDataList(dataList)
            val updatedSize = dataList.size
            recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
            loading = true
        }
    }
}

et l'ajouter à votre RecyclerView comme ceci

recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))

Pour un exemple de code complet, n'hésitez pas à consulter ce dépôt Github .

Kushal Sharma
la source
14
Une idée de comment appliquer cette solution avec un GridLayoutManager? Étant donné que la findFirstVisibleItemPosition()méthode n'est disponible qu'avec LinearLayoutManager, je ne peux pas comprendre comment la faire fonctionner.
MathieuMaree
1
Informations supplémentaires: dans ce cas, l'utilisation visibleItemCount = mLayoutManager.getChildCount();se comporte de la même manière que l'utilisation visibleItemCount = mRecyclerView.getChildCount();.
Jing Li
3
Placez le code dans la méthode onScrollStateChanged () au lieu de la méthode onScrolled ().
Anirudh
2
@ toobsco42 Vous pouvez utiliser ceci:int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
MathieuMaree
1
Qu'est-ce que ce findFirstVisibleItemPosition () non résolu
Suresh Sharma
72

Pour ceux qui ne veulent être avertis que lorsque le dernier élément est totalement affiché, vous pouvez utiliser View.canScrollVertically().

Voici ma mise en œuvre:

public abstract class OnVerticalScrollListener
        extends RecyclerView.OnScrollListener {

    @Override
    public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (!recyclerView.canScrollVertically(-1)) {
            onScrolledToTop();
        } else if (!recyclerView.canScrollVertically(1)) {
            onScrolledToBottom();
        } else if (dy < 0) {
            onScrolledUp();
        } else if (dy > 0) {
            onScrolledDown();
        }
    }

    public void onScrolledUp() {}

    public void onScrolledDown() {}

    public void onScrolledToTop() {}

    public void onScrolledToBottom() {}
}

Remarque: Vous pouvez utiliser recyclerView.getLayoutManager().canScrollVertically()si vous souhaitez prendre en charge l'API <14.

Hai Zhang
la source
Ceci est une réponse simple et géniale! Je vous remercie!
Andranik
2
Une solution similaire peut être utilisée pour tirer pour actualiser en vérifiant! RecyclerView.canScrollVertically (-1).
LordParsley
La meilleure solution sur le marché. Merci mec!
Angmar
1
@LordParsley Merci, mis à jour. C'est également l'un des moyens par lesquels SwipeRefreshLayout vérifie la traction à actualiser.
Hai Zhang
1
@EpicPandaForce Si vous utilisez, RecyclerViewvous aurez de la chance à nouveau, car vous pouvez simplement faire une copie statique de View.canScrollVertically()puisque les méthodes associées sont marquées publiques au lieu d'être protégées dans RecyclerView. Vous pouvez également étendre RecyclerViewpour réimplémenter canScrollVertically()si vous le souhaitez. Un troisième moyen simple consiste à appeler recyclerView.getLayoutManager().canScrollVertically(). Modifié dans ma réponse.
Hai Zhang
67

Voici une autre approche. Cela fonctionnera avec n'importe quel gestionnaire de mise en page.

  1. Rendre la classe de l'adaptateur abstraite
  2. Créez ensuite une méthode abstraite dans la classe de l'adaptateur (par exemple, load ())
  3. Dans onBindViewHolder, vérifiez la position du dernier et appelez load ()
  4. Remplacez la fonction load () lors de la création de l'objet adaptateur dans votre activité ou fragment.
  5. Dans la fonction de surcharge, implémentez votre appel loadmore

Pour une compréhension détaillée, j'ai écrit un article de blog et un exemple de projet. Téléchargez-le ici http://sab99r.com/blog/recyclerview-endless-load-more/

MyAdapter.java

public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            //check for last item
            if ((position >= getItemCount() - 1))
                load();
        }

        public abstract void load();
}

MyActivity.java

public class MainActivity extends AppCompatActivity {
    List<Items> items;
    MyAdapter adapter;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
    adapter=new MyAdapter(items){
            @Override
            public void load() {
                //implement your load more here
                Item lastItem=items.get(items.size()-1);
                loadMore();
            }
        };
   }
}
Sabeer Mohammed
la source
3
Comme pour la solution, vous n'avez pas besoin de rendre votre adaptateur abstrait, vous pouvez plutôt créer une interface dans votre adaptateur et faire en sorte que votre activité l'implémente en la gardant flexible et élégante, ex: ItemDisplayListener => onItemDisplayed (int position). De cette façon, vous pouvez charger à n'importe quelle position: au N, N-1, N-2 ..
Samer
1
@mcwise ajoute un booléen terminé = false; lorsque vous atteignez la fin, make done = true .. changez la condition if actuelle en if ((position> = getItemCount () - 1) && (! terminé))
Sabeer Mohammed
1
Cela devrait être marqué comme la bonne réponse. C'est élégant et simple.
Greg Ennis
2
Un bon exemple de la solution supérieure enfouie sous la popularité de la première solution. Très souvent, la dernière ligne sera liée à un type de vue distinct qui sert de bannière "Chargement ...". Lorsque ce type de vue reçoit un appel de liaison, il établit un rappel qui déclenche une notification de changement de données lorsque davantage de données sont disponibles. La réponse acceptée de connecter l'écouteur de défilement est un gaspillage et ne fonctionne probablement pas pour d'autres types de mise en page.
Michael Hays
3
@mcwise, il n'appellerait sans fin que si l'utilisateur faisait activement défiler et revenir au dernier élément. onBindViewHolder n'est appelé qu'une fois à chaque fois que la vue entre dans l'écran et ne l'appelle pas constamment.
David Liu
18

Ma réponse est une version modifiée de Noor. Je suis passé d'un ListViewendroit où j'avais EndlessScrollListener(que vous pouvez trouver facilement dans de nombreuses réponses sur SO) à un RecyclerViewdonc je voulais un mettre EndlessRecyclScrollListenerà jour facilement mon ancien auditeur.

Voici donc le code, j'espère qu'il vous aidera:

public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
    // The total number of items in the dataset after the last load
    private int previousTotalItemCount = 0;
    private boolean loading = true;
    private int visibleThreshold = 5;
    int firstVisibleItem, visibleItemCount, totalItemCount;
    private int startingPageIndex = 0;
    private int currentPage = 0;

    @Override
    public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
    {
        super.onScrolled(mRecyclerView, dx, dy);
        LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
                .getLayoutManager();

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
        onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
    }

    public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        // If the total item count is zero and the previous isn't, assume the
        // list is invalidated and should be reset back to initial state
        if (totalItemCount < previousTotalItemCount)
        {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0)
            {
                this.loading = true;
            }
        }
        // If it’s still loading, we check to see if the dataset count has
        // changed, if so we conclude it has finished loading and update the current page
        // number and total item count.
        if (loading && (totalItemCount > previousTotalItemCount))
        {
            loading = false;
            previousTotalItemCount = totalItemCount;
            currentPage++;
        }

        // If it isn’t currently loading, we check to see if we have breached
        // the visibleThreshold and need to reload more data.
        // If we do need to reload some more data, we execute onLoadMore to fetch the data.
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
                visibleThreshold))
        {
            onLoadMore(currentPage + 1, totalItemCount);
            loading = true;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page, int totalItemsCount);

}
Federico Ponzi
la source
1
Votre implémentation nécessite que RecyclerView utilise LinearLeayoutManager. Mauvaise idée!
Orri
@Orri Pourriez-vous expliquer pourquoi? Merci!
Federico Ponzi
1
Dans votre onScrolled, vous convertissez le LayoutManager de RecyclerView en LinearLayoutManager. Cela ne fonctionne pas pour StaggredGridLayout ou autre chose. Il n'y a pas findFirstVisibleItemPosition () dans StaggredGridLayout.
Orri
@Orri Merci de l'avoir signalé! Mais je ne pense pas que ce soit une si mauvaise idée, car cela a fonctionné pour moi et, en fonction des votes positifs, d'autres cas :)
Federico Ponzi
10

Pour moi, c'est très simple:

     private boolean mLoading = false;

     mList.setOnScrollListener(new RecyclerView.OnScrollListener() {

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            int totalItem = mLinearLayoutManager.getItemCount();
            int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();

            if (!mLoading && lastVisibleItem == totalItem - 1) {
                mLoading = true;
                // Scrolled to bottom. Do something here.
                mLoading = false;
            }
        }
    });

Soyez prudent avec les travaux asynchrones: le chargement du mL doit être modifié à la fin des travaux asynchrones. J'espère que ce sera utile!

Minh Nguyen
la source
J'ai trouvé cette réponse après des jours de questions moi-même, essentiellement. C'est la meilleure solution, imo.
wsgeorge
s'il vous plaît donner une solution pour recyclerview avec le numéro de page loadmore passant android
Harsha
getWebServiceData (); mStoresAdapterData.setOnLoadMoreListener (new StoresAdapter.OnLoadMoreListener () {@Override public void onLoadMore () {// add null, afin que l'adaptateur vérifie view_type et affiche la barre de progression en bas storesList.add (null); mStoresAdapterData.notifyItemInsize (stores) () - 1); ++ pageNumber; getWebServiceData ();}}); les deux appelant l'un après l'autre enfin les données de la 2e page seulement visibles, veuillez aider
Harsha
j'ai essayé de nombreuses implémentations de la pagination recyclerView mais aucune d'entre elles n'a fonctionné, sauf celle-ci, vous avez mon vote positif
Mohammed Elrashied
9

La plupart des réponses supposent que les RecyclerViewutilisations a LinearLayoutManager, ou GridLayoutManager, ou même StaggeredGridLayoutManager, ou supposent que le défilement est vertical ou horyzontal, mais personne n'a posté une réponse complètement générique .

L'utilisation de l' ViewHolderadaptateur n'est clairement pas une bonne solution. Un adaptateur peut avoir plus de 1 RecyclerViewutilisateur. Il "adapte" leur contenu. Ce doit être la vue Recycleur (qui est la seule classe qui est responsable de ce qui est actuellement affiché pour l'utilisateur, et non l'adaptateur qui n'est responsable que de fournir du contenu à RecyclerView) qui doit informer votre système que plus d'éléments sont nécessaires (pour charge).

Voici ma solution, en utilisant rien d'autre que les classes abstraites de RecyclerView (RecycerView.LayoutManager et RecycerView.Adapter):

/**
 * Listener to callback when the last item of the adpater is visible to the user.
 * It should then be the time to load more items.
 **/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
      super.onScrolled(recyclerView, dx, dy);

      // init
      RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
      RecyclerView.Adapter adapter = recyclerView.getAdapter();

      if (layoutManager.getChildCount() > 0) {
        // Calculations..
        int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
        View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
        int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
        boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);

        // check
        if (isLastItemVisible)
          onLastItemVisible(); // callback
     }
   }

   /**
    * Here you should load more items because user is seeing the last item of the list.
    * Advice: you should add a bollean value to the class
    * so that the method {@link #onLastItemVisible()} will be triggered only once
    * and not every time the user touch the screen ;)
    **/
   public abstract void onLastItemVisible();

}


// --- Exemple of use ---

myRecyclerView.setOnScrollListener(new LastItemListener() {
    public void onLastItemVisible() {
         // start to load more items here.
    }
}
Yairopro
la source
1
Votre réponse est juste et cela a fonctionné pour tous, LayoutManagersmais un seul instant: déplacez votre code de calcul dans une autre méthode scrollListener - déplacez-le à la onScrollStateChangedplace onScrolled. Et pour onScrollStateChangedvérifier:if(newState == RecyclerView.SCROLL_STATE_IDLE) { // calculation code }
Trancer
Merci pour vos conseils, mais je ne pense pas que ce serait une meilleure solution. Le développeur souhaite que son écouteur soit déclenché avant même que la vue de recyclage cesse de défiler. Il peut donc finir de charger de nouveaux éléments de données (si les données sont stockées localement), ou même ajouter un support de vue de barre de progression à la fin de la vue de recyclage, donc lorsque la vue de recyclage s'arrêtera pour défiler, elle peut être sur une progression de cercle -bar comme dernier élément visible.
Yairopro
6

Bien que la réponse acceptée fonctionne parfaitement, la solution ci-dessous utilise addOnScrollListener car setOnScrollListener est obsolète et réduit le nombre de variables et les conditions if.

final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);

feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (dy > 0) {   
            if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
                Log.d("TAG", "End of list");
                //loadMore();
            }
        }
    }
});
capt.swag
la source
1
Parfois, ma méthode loadMore () s'appelait trois fois dans onScrolled (), Toute idée pourquoi elle est appelée trois fois et comment arrêter de l'appeler plusieurs fois.
ved
dy> 0 est toujours faux
Dushyant Suthar
@ved C'est parce que le défilement a 3 états: défilement, défilement, inactif. Vous choisissez celui qui convient.
TheRealChx101
5

Bien qu'il y ait tant de réponses à la question, je voudrais partager notre expérience de la création de la vue de liste sans fin. Nous avons récemment implémenté un carrousel LayoutManager personnalisé qui peut fonctionner dans le cycle en faisant défiler la liste à l'infini ainsi que jusqu'à un certain point. Voici une description détaillée de GitHub .

Je vous suggère de jeter un œil à cet article avec des recommandations courtes mais utiles sur la création de LayoutManagers personnalisés: http://cases.azoft.com/create-custom-layoutmanager-android/

Vladimir Tchernitski
la source
5

Grâce à la puissance des fonctions d'extension de Kotlin, le code peut paraître beaucoup plus élégant. Mettez cela où vous voulez (je l'ai dans un fichier ExtensionFunctions.kt):

/**
 * WARNING: This assumes the layout manager is a LinearLayoutManager
 */
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){

    this.addOnScrollListener(object: RecyclerView.OnScrollListener(){

        private val VISIBLE_THRESHOLD = 5

        private var loading = true
        private var previousTotal = 0

        override fun onScrollStateChanged(recyclerView: RecyclerView,
                                          newState: Int) {

            with(layoutManager as LinearLayoutManager){

                val visibleItemCount = childCount
                val totalItemCount = itemCount
                val firstVisibleItem = findFirstVisibleItemPosition()

                if (loading && totalItemCount > previousTotal){

                    loading = false
                    previousTotal = totalItemCount
                }

                if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){

                    onScrolledToEnd()
                    loading = true
                }
            }
        }
    })
}

Et puis utilisez-le comme ceci:

youRecyclerView.addOnScrolledToEnd {
    //What you want to do once the end is reached
}

Cette solution est basée sur la réponse de Kushal Sharma. Cependant, c'est un peu mieux car:

  1. Il utilise onScrollStateChangedau lieu de onScroll. C'est mieux car il onScrollest appelé à chaque fois qu'il y a une sorte de mouvement dans le RecyclerView, alors qu'il onScrollStateChangedn'est appelé que lorsque l'état du RecyclerView est modifié. En utilisantonScrollStateChanged permettra d'économiser du temps CPU et, par conséquent, de la batterie.
  2. Comme cela utilise des fonctions d'extension, cela peut être utilisé dans n'importe quel RecyclerView que vous possédez. Le code client est composé d'une seule ligne.
Tiago
la source
Lorsque j'ai SwipeRefreshLayout's setOnRefreshListener the addOnScrolledToEnd is not working tiré la classe interne privée PullToRefresh: SwipeRefreshLayout.OnRefreshListener {override fun onRefresh () {pbViewMore !!. Visibilité = View.GONE pageCount = 0 courseList.clear () // appel asynctask here srlNoMessageRefreshLayout !!. IsRefreshing = false swipe ! .isRefreshing = false}}
Naveen Kumar M
5

Voici comment je le fais, simple et court:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            if(!recyclerView.canScrollVertically(1) && dy != 0)
            {
                // Load more results here

            }
        }
    });
John T
la source
3

OK, je l'ai fait en utilisant la méthode onBindViewHolder de RecyclerView.Adapter.

Adaptateur:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    ...

    if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}

Fragment (ou activité) :

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    contentView = inflater.inflate(R.layout.comments_list, container, false);
    recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
    adapter = new Adapter();
    recyclerView.setAdapter(adapter);

    ...

    adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
        @Override
        public void onRequestedLastItem() {
            //TODO fetch new data from webservice
        }
    });
    return contentView;
}
erdna
la source
1
compter sur onBind n'est pas une très bonne idée. RecyclerView met en cache les vues + certains gestionnaires de mise en page ont des moyens de pré-mettre en cache les choses. Je suggère de définir un écouteur de défilement et lorsque l'écouteur de défilement s'arrête, obtenez la dernière position d'élément visible à partir du gestionnaire de disposition. Si vous utilisez des gestionnaires de disposition par défaut, ils ont tous des méthodes findLastVisible **.
yigit
@yigit À quelle distance RecyclerView tentera-t-il de mettre en cache les choses? Si vous avez 500 éléments, je doute vraiment qu'il se cache si loin (sinon ce sera juste un énorme gaspillage de performances). Quand il commencera réellement à mettre en cache (même lorsque nous sommes à 480 éléments), cela signifie qu'il est quand même temps de charger un autre lot.
Alex Orlov
1
Il fait des choses pré-cachées à moins qu'il ne défile en douceur vers une position éloignée. Dans ce cas, LLM (par défaut) met en page deux pages au lieu d'une afin qu'il puisse trouver la vue cible et y décélérer. Il met en cache les vues qui sortent des limites. Par défaut, cette taille de cache est de 2 et peut être configurée via setItemCacheSize.
yigit
1
@yigit réfléchissant juste au problème, je ne vois pas comment la mise en cache pourrait présenter un problème ici; nous ne sommes intéressés que la première fois que les "derniers" articles sont liés de toute façon! Manquer l'appel à onBindViewHolder lorsque le gestionnaire de disposition réutilise une vue mise en cache qui était déjà liée n'est tout simplement pas un problème lorsque nous sommes intéressés par la pagination dans plus de données à la fin de la liste. Correct?
Greg Ennis
2
Dans ma bibliothèque FlexibleAdapter, j'ai choisi d'adopter le défilement sans fin avec l'appel onBindViewHolder(), cela fonctionne comme un charme. L'implémentation est minuscule par rapport à l'écouteur de défilement et elle est plus facile à utiliser.
Davideas
3

Ma façon de détecter un événement de chargement n'est pas de détecter le défilement, mais d'écouter si la dernière vue était attachée. Si la dernière vue était jointe, je considère que c'est le moment de charger plus de contenu.

class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
    RecyclerView mRecyclerView;

    MyListener(RecyclerView view) {
        mRecyclerView = view;
    }

    @Override
    public void onChildViewAttachedToWindow(View view) {

    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
    int adapterPosition = mgr.getPosition(view);

    if (adapterPosition == adapter.getItemCount() - 1) {
        // last view was attached
        loadMoreContent();
    }

    @Override
    public void onChildViewDetachedFromWindow(View view) {}
}
walkingice
la source
2

J'essaierais d'étendre la méthode utilisée LayoutManager(par exemple LinearLayoutManager) et de remplacer scrollVerticallyBy(). Tout d'abord, j'appellerais d' superabord, puis vérifierais la valeur entière retournée. Si la valeur est égale à, 0une bordure inférieure ou supérieure est atteinte. Ensuite, j'utiliserais une findLastVisibleItemPosition()méthode pour savoir quelle frontière est atteinte et charger plus de données si nécessaire. Juste une idée.

De plus, vous pouvez même renvoyer votre valeur à partir de cette méthode permettant un défilement excessif et montrant un indicateur de "chargement".

sergej shafarenka
la source
2
 recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener() 
            {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx,int dy)
                {
                    super.onScrolled(recyclerView, dx, dy); 
                }

                @Override
                public void onScrollStateChanged(RecyclerView recyclerView,int newState) 
                {
                    int totalItemCount = layoutManager.getItemCount();
                    int lastVisibleItem = layoutManager.findLastVisibleItemPosition();

                    if (totalItemCount> 1) 
                    {
                        if (lastVisibleItem >= totalItemCount - 1) 
                        {
                            // End has been reached
                            // do something 
                        }
                    }          
                }
            });  
Anirudh
la source
1
onScrollStateChanged()ne fonctionnera pas car il ne sera appelé que lorsque vous modifiez l'état du défilement et non pendant le défilement. Vous devez utiliser la onScrolled()méthode.
mradzinski
2

J'ai réalisé une implémentation de type défilement infini en utilisant cette logique dans la onBindViewHolderméthode de ma RecyclerView.Adapterclasse.

    if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
        if (mCurrentPage == mTotalPageCount) {
            mLoadImagesListener.noMorePages();
        } else {
            int newPage = mCurrentPage + 1;
            mLoadImagesListener.loadPage(newPage);
        }
    }

Avec ce code, lorsque RecyclerView atteint le dernier élément, il incrémente la page en cours et les rappels sur une interface qui est chargée de charger plus de données à partir de l'API et d'ajouter les nouveaux résultats à l'adaptateur.

Je peux poster un exemple plus complet si ce n'est pas clair?

speedynomads
la source
Je sais que ce post est vraiment ancien, mais je suis un peu nouveau et je ne sais pas où trouver les variables que vous comparez. mCurrentPage et mTotalPageCount font référence à votre ensemble de données, je suppose, et mItems est la liste des éléments de la liste, mais comment puis-je trouver la position?
BooleanCheese
Vous pouvez voir comment j'ai implémenté mon RecyclerView.Adapter ici: github.com/dominicthomas/FlikrGridRecyclerView/blob/master/app/…
speedynomads
2

Pour les personnes qui utilisent StaggeredGridLayoutManager voici mon implémentation, cela fonctionne pour moi.

 private class ScrollListener extends RecyclerView.OnScrollListener {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);

        if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
            loadMoreImages();
        }

    }

    private boolean loadMoreImages(){
        Log.d("myTag", "LAST-------HERE------");
        return true;
    }
}
Dennis Zinkovski
la source
@SudheeshMohan Voici une classe complète, c'est petit) Dans mon projet, je change dynamiquement le nombre de travées, et cela fonctionnera pour 1 et 2 comptages de travées
Dennis Zinkovski
recyclerView.canScrollVertically (1) le fera mais il nécessite l'API 14. :(
Sudheesh Mohan
2

Il existe une bibliothèque facile à utiliser pour cette pagination nommée . Prend en charge ListView et RecyclerView (LinearLayout, GridLayout et StaggeredGridLayout).

Voici le lien vers le projet sur Github

nonybrighto
la source
2
  1. Créez une classe abstraite et étend RecyclerView.OnScrollListener

    public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    private int previousTotal = 0;
    private boolean loading = true;
    private int visibleThreshold;
    private int firstVisibleItem, visibleItemCount, totalItemCount;
    private RecyclerView.LayoutManager layoutManager;
    
    public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
    this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    
    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = layoutManager.getItemCount();
    firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
    
    if (loading) {
        if (totalItemCount > previousTotal) {
            loading = false;
            previousTotal = totalItemCount;
        }
    }
    if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
        onLoadMore();
        loading = true;
    }
      }
    
    public abstract void onLoadMore();}
  2. en activité (ou fragment) ajouter addOnScrollListener à recyclerView

    LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
        @Override
        public void onLoadMore() {
            //TODO
            ...
        }
    });
roghayeh hosseini
la source
1

J'ai un exemple assez détaillé de la pagination avec un RecyclerView. À un niveau élevé, j'ai un ensemble PAGE_SIZE, disons 30. Donc je demande 30 articles et si j'en récupère 30, je demande la page suivante. Si je reçois moins de 30 éléments, je signale une variable pour indiquer que la dernière page a été atteinte, puis j'arrête de demander plus de pages. Vérifiez-le et faites-moi savoir ce que vous en pensez.

https://medium.com/@etiennelawlor/pagination-with-recyclerview-1cb7e66a502b

toobsco42
la source
1

Ici, ma solution utilisant AsyncListUtil , sur le Web, dit: Notez que cette classe utilise un seul thread pour charger les données, donc elle convient pour charger des données à partir d'un stockage secondaire tel qu'un disque, mais pas à partir d'un réseau. mais j'utilise odata pour lire les données et travailler correctement. Je manque dans mon exemple les entités de données et les méthodes de réseau. J'inclus uniquement l'exemple d'adaptateur.

public class AsyncPlatoAdapter extends RecyclerView.Adapter {

    private final AsyncPlatoListUtil mAsyncListUtil;
    private final MainActivity mActivity;
    private final RecyclerView mRecyclerView;
    private final String mFilter;
    private final String mOrderby;
    private final String mExpand;

    public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
        mFilter = filter;
        mOrderby = orderby;
        mExpand = expand;
        mRecyclerView = recyclerView;
        mActivity = activity;
        mAsyncListUtil = new AsyncPlatoListUtil();

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).
                inflate(R.layout.plato_cardview, parent, false);

        // Create a ViewHolder to find and hold these view references, and
        // register OnClick with the view holder:
        return new PlatoViewHolderAsync(itemView, this);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final Plato item = mAsyncListUtil.getItem(position);
        PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
        if (item != null) {
            Integer imagen_id = item.Imagen_Id.get();
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            vh.getImage().setVisibility(View.VISIBLE);
            vh.getProgress().setVisibility(View.GONE);
            String cacheName = null;
            String urlString = null;
            if (imagen_id != null) {
                cacheName = String.format("imagenes/imagen/%d", imagen_id);
                urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
            }
            ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
        } else {
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            //show progress while loading.
            vh.getImage().setVisibility(View.GONE);
            vh.getProgress().setVisibility(View.VISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return mAsyncListUtil.getItemCount();
    }

    public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
        /**
         * Creates an AsyncListUtil.
         */
        public AsyncPlatoListUtil() {
            super(Plato.class, //my data class
                    10, //page size
                    new DataCallback<Plato>() {
                        @Override
                        public int refreshData() {
                            //get count calling ../$count ... odata endpoint
                            return countPlatos(mFilter, mOrderby, mExpand, mActivity);
                        }

                        @Override
                        public void fillData(Plato[] data, int startPosition, int itemCount) {
                            //get items from odata endpoint using $skip and $top
                            Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
                            for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
                                data[i] = p.value.get(i);
                            }

                        }
                    }, new ViewCallback() {
                        @Override
                        public void getItemRangeInto(int[] outRange) {
                            //i use LinearLayoutManager in the RecyclerView
                            LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
                            outRange[0] = layoutManager.findFirstVisibleItemPosition();
                            outRange[1] = layoutManager.findLastVisibleItemPosition();
                        }

                        @Override
                        public void onDataRefresh() {
                            mRecyclerView.getAdapter().notifyDataSetChanged();
                        }

                        @Override
                        public void onItemLoaded(int position) {
                            mRecyclerView.getAdapter().notifyItemChanged(position);
                        }
                    });
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    onRangeChanged();
                }
            });
        }
    }
}
FRL
la source
1

@kushal @abdulaziz

Pourquoi ne pas utiliser cette logique à la place?

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    int totalItemCount, lastVisibleItemPosition;

    if (dy > 0) {
      totalItemCount = _layoutManager.getItemCount();
      lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();

      if (!_isLastItem) {
        if ((totalItemCount - 1) == lastVisibleItemPosition) {
          LogUtil.e("end_of_list");

          _isLastItem = true;
        }
      }
    }
  }
leonapse
la source
Mon onScroller () a appelé trois fois pour un seul élément, donc j'obtiens trois fois end_of_list et ma logique de pagination s'appelle trois fois. Comment corriger cela pour appeler la pagination une seule fois.
ved
@ved Ce n'est pas censé se produire. Ce code ci-dessus garantit que la end_of_listcondition ne sera appelée qu'une seule fois. L'indicateur _isLastItemest une variable globale dans l'activité qui a une valeur par défaut de false. Ce que j'ai fait, c'est d'exécuter une tâche en arrière-plan après avoir détecté la fin de la liste, récupéré la page suivante, puis notifié l'adaptateur du nouvel ensemble de données, et enfin défini _isLastItemà nouveau sur false.
leonapse
1

Essayez ci-dessous:

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;

/**
 * Abstract Endless ScrollListener
 * 
 */
public abstract class EndlessScrollListener extends
        RecyclerView.OnScrollListener {
    // The minimum amount of items to have below your current scroll position
    // before loading more.
    private int visibleThreshold = 10;
    // The current offset index of data you have loaded
    private int currentPage = 1;
    // True if we are still waiting for the last set of data to load.
    private boolean loading = true;
    // The total number of items in the data set after the last load
    private int previousTotal = 0;
    private int firstVisibleItem;
    private int visibleItemCount;
    private int totalItemCount;
    private LayoutManager layoutManager;

    public EndlessScrollListener(LayoutManager layoutManager) {
        validateLayoutManager(layoutManager);
        this.layoutManager = layoutManager;
    }

    public EndlessScrollListener(int visibleThreshold,
            LayoutManager layoutManager, int startPage) {
        validateLayoutManager(layoutManager);
        this.visibleThreshold = visibleThreshold;
        this.layoutManager = layoutManager;
        this.currentPage = startPage;
    }

    private void validateLayoutManager(LayoutManager layoutManager)
            throws IllegalArgumentException {
        if (null == layoutManager
                || !(layoutManager instanceof GridLayoutManager)
                || !(layoutManager instanceof LinearLayoutManager)) {
            throw new IllegalArgumentException(
                    "LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = layoutManager.getItemCount();
        if (layoutManager instanceof GridLayoutManager) {
            firstVisibleItem = ((GridLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            firstVisibleItem = ((LinearLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        }
        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading
                && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached do something
            currentPage++;
            onLoadMore(currentPage);
            loading = true;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page);

}
shridutt kothari
la source
1

J'ai créé LoadMoreRecyclerView en utilisant Abdulaziz Noor Answer

LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView  {

    private boolean loading = true;
    int pastVisiblesItems, visibleItemCount, totalItemCount;
    //WrapperLinearLayout is for handling crash in RecyclerView
    private WrapperLinearLayout mLayoutManager;
    private Context mContext;
    private OnLoadMoreListener onLoadMoreListener;

    public LoadMoreRecyclerView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init(){
        mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
        this.setLayoutManager(mLayoutManager);
        this.setItemAnimator(new DefaultItemAnimator());
        this.setHasFixedSize(true);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        super.onScrolled(dx, dy);

        if(dy > 0) //check for scroll down
        {
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading)
            {
                if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
                {
                    loading = false;
                    Log.v("...", "Call Load More !");
                    if(onLoadMoreListener != null){
                        onLoadMoreListener.onLoadMore();
                    }
                    //Do pagination.. i.e. fetch new data
                }
            }
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
    }

    public void onLoadMoreCompleted(){
        loading = true;
    }

    public void setMoreLoading(boolean moreLoading){
        loading = moreLoading;
    }

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }
}

WrapperLinearLayout

public class WrapperLinearLayout extends LinearLayoutManager
{
    public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("probe", "meet a IOOBE in RecyclerView");
        }
    }
}

// Ajoutez-le en xml comme

<your.package.LoadMoreRecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>

OnCreate ou onViewCreated

mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                callYourService(StartIndex);
            }
        });

callYourService

private void callYourService(){
    //callyour Service and get response in any List

    List<AnyModelClass> newDataFromServer = getDataFromServerService();
    //Enable Load More
    mLoadMoreRecyclerView.onLoadMoreCompleted();

    if(newDataFromServer != null && newDataFromServer.size() > 0){
            StartIndex += newDataFromServer.size();

            if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
                    //StopLoading..
                   mLoadMoreRecyclerView.setMoreLoading(false);
            }
    }
    else{
            mLoadMoreRecyclerView.setMoreLoading(false);
            mAdapter.notifyDataSetChanged();
    }
}
Zar E Ahmer
la source
1

Il est également possible de l'implémenter sans l'écouteur de défilement, en utilisant uniquement la logique pure du modèle de données. La vue de défilement nécessite d'obtenir les articles par position ainsi que le nombre maximal d'articles. Le modèle peut avoir la logique d'arrière-plan pour extraire les éléments nécessaires en morceaux, plutôt qu'un par un, et le faire dans le thread d'arrière-plan, en avertissant la vue lorsque les données sont prêtes.

Cette approche permet d'avoir la file d'attente de récupération qui préfère les éléments les plus récemment demandés (donc actuellement visibles) aux soumissions plus anciennes (probablement déjà défilées), de contrôler le nombre de threads parallèles à utiliser et d'autres choses du même genre. Le code source complet de cette approche (application de démonstration et bibliothèque réutilisable) est disponible ici .

Audrius Meskauskas
la source
0

Il existe une méthode public void setOnScrollListener (RecyclerView.OnScrollListener listener)dans https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29 . Utiliser ça

ÉDITER:

Remplacez la onScrollStateChangedméthode à l'intérieur du onScrollListeneret procédez comme suit

            boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;

            //loading is used to see if its already loading, you have to manually manipulate this boolean variable
            if (loadMore && !loading) {
                 //end of list reached
            }
Panthère
la source
1
Soyez plus précis! Comment utiliser RecyclerView.OnScrollListener pour déterminer que l'utilisateur a défilé jusqu'à la fin de la liste?
erdna
Il n'y a pas d'onScroll à RecyclerView.OnScrollListener ! Vous mélangez AbsListView.OnScrollListener avec RecyclerView.OnScrollListener.
erdna
ya, il est censé êtreonScrollStateChanged
Panther
onScrollStateChangedne fonctionnera pas car il ne sera appelé que lorsque vous modifiez l'état du défilement et non pendant le défilement.
Pedro Oliveira
@PedroOliveira Est-il possible onScrollStateChangedde déterminer que l'utilisateur fait défiler jusqu'au dernier élément?
erdna
0

Vérifiez que tout est expliqué en détail: Pagination à l'aide de RecyclerView de A à Z

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView,
                                     int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int visibleItemCount = mLayoutManager.getChildCount();
        int totalItemCount = mLayoutManager.getItemCount();
        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();

        if (!mIsLoading && !mIsLastPage) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                    && firstVisibleItemPosition >= 0) {
                loadMoreItems();
            }
        }
    }
})

loadMoreItems ():

private void loadMoreItems() {
    mAdapter.removeLoading();
    //load data here from the server

    // in case of success
    mAdapter.addData(data);
    // if there might be more data
    mAdapter.addLoading();
}

dans MyAdapter:

private boolean mIsLoadingFooterAdded = false;

public void addLoading() {
    if (!mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = true;
        mLineItemList.add(new LineItem());
        notifyItemInserted(mLineItemList.size() - 1);
    }
}

public void removeLoading() {
    if (mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = false;
        int position = mLineItemList.size() - 1;
        LineItem item = mLineItemList.get(position);

        if (item != null) {
            mLineItemList.remove(position);
            notifyItemRemoved(position);
        }
    }
}

public void addData(List<YourDataClass> data) {

    for (int i = 0; i < data.size(); i++) {
        YourDataClass yourDataObject = data.get(i);
        mLineItemList.add(new LineItem(yourDataObject));
        notifyItemInserted(mLineItemList.size() - 1);
    }
}
awsleiman
la source
Le lien peut contenir une réponse, mais essayez d'expliquer certaines parties substantielles des explications dans le lien dans votre message.
Ian
0

Aucune de ces réponses ne prend en compte si la liste est trop petite ou non.

Voici un morceau de code que j'utilise qui fonctionne sur RecycleViews dans les deux sens.

@Override
    public boolean onTouchEvent(MotionEvent motionEvent) {

        if (recyclerViewListener == null) {
            return super.onTouchEvent(motionEvent);
        }

        /**
         * If the list is too small to scroll.
         */
        if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
            if (!canScrollVertically(1)) {
                recyclerViewListener.reachedBottom();
            } else if (!canScrollVertically(-1)) {
                recyclerViewListener.reachedTop();
            }
        }

        return super.onTouchEvent(motionEvent);
    }

    public void setListener(RecyclerViewListener recycleViewListener) {
        this.recyclerViewListener = recycleViewListener;
        addOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if (recyclerViewListener == null) {
                    return;
                }

                recyclerViewListener.scrolling(dy);

                if (!canScrollVertically(1)) {
                    recyclerViewListener.reachedBottom();
                } else if (!canScrollVertically(-1)) {
                    recyclerViewListener.reachedTop();
                }
            }

        });
    }
Oliver Dixon
la source
0

Je vous laisse mon aproximation. Fonctionne bien pour moi.

J'espère que ça t'aide.

/**
 * Created by Daniel Pardo Ligorred on 03/03/2016.
 */
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {

    protected RecyclerView.LayoutManager layoutManager;

    public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {

        this.layoutManager = layoutManager;

        this.init();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        super.onScrolled(recyclerView, dx, dy);

        this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
    }

    private int getFirstVisibleItem(){

        if(this.layoutManager instanceof LinearLayoutManager){

            return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
        } else if (this.layoutManager instanceof StaggeredGridLayoutManager){

            int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.

            try{

                return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
            }catch (Exception ex){

                // Do stuff...
            }
        }

        return 0;
    }

    public abstract void init();

    protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);

}
Dani
la source
0

Cette solution fonctionne parfaitement pour moi.

//Listener    

public abstract class InfiniteScrollListener     extendsRecyclerView.OnScrollListener {

public static String TAG = InfiniteScrollListener.class.getSimpleName();
int firstVisibleItem, visibleItemCount, totalItemCount;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 1;
private int current_page = 1;

private LinearLayoutManager mLinearLayoutManager;

public InfiniteScrollListener(LinearLayoutManager linearLayoutManager) {
    this.mLinearLayoutManager = linearLayoutManager;
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);

    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = mLinearLayoutManager.getItemCount();
    firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

    if (loading) {
        if (totalItemCount > previousTotal) {
            loading = false;
            previousTotal = totalItemCount;
        }
    }
    if (!loading && (totalItemCount - visibleItemCount - firstVisibleItem <= visibleThreshold)) {

        current_page++;

        onLoadMore(current_page);

        loading = true;
    }
}

public void resetState() {
    loading = true;
    previousTotal = 0;
    current_page = 1;
}

public abstract void onLoadMore(int current_page);
}

//Implementation into fragment 
 private InfiniteScrollListener scrollListener;

scrollListener = new InfiniteScrollListener(manager) {
        @Override
        public void onLoadMore(int current_page) {
            //Load data
        }
    };
    rv.setLayoutManager(manager);
    rv.addOnScrollListener(scrollListener);
Giedrius Šlikas
la source