¿Cómo implementar una lista interminable con RecyclerView?

346

Me gustaría cambiar ListViewa RecyclerView. Quiero usar el onScrollde OnScrollListenerenRecyclerView para determinar si un usuario se desplazó hasta el final de la lista.

¿Cómo sé si un usuario se desplaza hasta el final de la lista para poder obtener nuevos datos de un servicio REST?

erdna
fuente
Por favor revise este enlace Le ayudará ... stackoverflow.com/questions/26293461/…
samsad
32
¡Por favor lea la pregunta cuidadosamente! Sé cómo hacer esto con un ListView pero NO cómo implementarlo con un RecycleView .
erdna
cómo implementar loadmore onscrolllistener en android.data se carga pero se actualiza en los datos existentes, por favor ayúdenme
Harsha
2
Puede usar esta biblioteca: github.com/rockerhieu/rv-adapter-endless . Se basa en la idea de cwac-endlesspara ListView.
Hieu Rocker

Respuestas:

422

Gracias a @Kushal y así es como lo implementé

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

No olvides agregar

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Abdulaziz Noor
fuente
11
¿Qué puedo hacer por?StaggeredGridLayoutManager
Pratik Butani
16
¿Qué pasamLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1
Laaptu
45
Para todos los que findFirstVisibleItemPosition()no se hayan resuelto, deben cambiar RecyclerView.LayoutManageraLinearLayoutManager
Amr Mohammed
26
¿Dónde está la condición para hacer de loading = truenuevo?
Bhargav
10
No olvides agregar loading = truedespués de la //Do paginationparte. de lo contrario, este código se ejecutará solo una vez y no podrá cargar más elementos.
Shaishav Jogani
165

Haz estas variables.

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

Establecer en Desplazar para ver la vista del reciclador.

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

Nota: Asegúrese de estar utilizando LinearLayoutManagercomo administrador de diseño para RecyclerView.

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

y para una cuadrícula

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

Diviértete con tus pergaminos sin fin !! ^. ^

Actualización: mRecyclerView. ¡setOnScrollListener () está en desuso, simplemente reemplácelo mRecyclerView.addOnScrollListener()y la advertencia desaparecerá! Puedes leer más de esta pregunta SO .

Dado que Android ahora admite oficialmente Kotlin, aquí hay una actualización para el mismo:

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

y agréguelo a su RecyclerView como este

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

Para un ejemplo de código completo, no dude en consultar este repositorio de Github .

Kushal Sharma
fuente
14
¿Alguna idea de cómo aplicar esta solución con un GridLayoutManager? Como el findFirstVisibleItemPosition()método solo está disponible con LinearLayoutManager, no puedo entender cómo hacerlo funcionar.
MathieuMaree
1
Información adicional: en este caso, usar se visibleItemCount = mLayoutManager.getChildCount();comporta igual que usar visibleItemCount = mRecyclerView.getChildCount();.
Jing Li
3
Coloque el código en el método onScrollStateChanged () en lugar del método onScrolled ().
Anirudh
2
@ toobsco42 Puede usar esto:int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
MathieuMaree
1
¿Qué es esto findFirstVisibleItemPosition () no resuelto
Suresh Sharma
72

Para aquellos que solo desean recibir una notificación cuando se muestre totalmente el último elemento, pueden usarlo View.canScrollVertically().

Aquí está mi implementación:

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

Nota: puede usar recyclerView.getLayoutManager().canScrollVertically()si desea admitir API <14.

Hai Zhang
fuente
¡Esta es una respuesta excelente y simple! ¡Gracias!
Andranik
2
Se puede usar una solución similar para extraer para actualizar marcando! RecyclerView.canScrollVertically (-1).
LordParsley
La mejor solución por ahí. Gracias amigo
Angmar
1
@ LordParsley Gracias, actualizado. Esta también es una de las formas en que SwipeRefreshLayout comprueba la extracción para actualizar.
Hai Zhang
1
@EpicPandaForce Si está utilizando RecyclerView, tendrá suerte nuevamente, porque simplemente puede hacer una copia estática de, View.canScrollVertically()ya que los métodos relacionados están marcados como públicos en lugar de protegidos RecyclerView. También puedes extenderRecyclerView para volver a implementar canScrollVertically()si lo desea. Una tercera y simple forma es llamar recyclerView.getLayoutManager().canScrollVertically(). Editado en mi respuesta.
Hai Zhang el
67

Aquí hay otro enfoque. Funcionará con cualquier administrador de diseño.

  1. Hacer resumen de clase de adaptador
  2. Luego, cree un método abstracto en la clase de adaptador (por ejemplo, load ())
  3. En onBindViewHolder verifique la posición si es la última y llame a load ()
  4. Anule la función load () mientras crea el objeto adaptador en su actividad o fragmento.
  5. En la función de carga sobrecargada, implemente su llamada loadmore

Para una comprensión detallada, escribí una publicación de blog y un proyecto de ejemplo, consígalo aquí 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
fuente
3
Al igual que la solución, no tiene que hacer que su adaptador sea abstracto, en su lugar, puede crear una interfaz en su adaptador y hacer que su actividad lo implemente manteniéndolo flexible y elegante, por ejemplo: ItemDisplayListener => onItemDisplayed (posición int). De esta manera puede cargar en cualquier posición: en N, N-1, N-2 ..
Samer
1
@mcwise agrega un booleano terminado = falso; cuando llegue al final, haga terminado = verdadero ... cambie la condición if actual a if ((position> = getItemCount () - 1) && (! terminado))
Sabeer Mohammed
1
Esto debe marcarse como la respuesta correcta. Esto es elegante y simple.
Greg Ennis
2
Un buen ejemplo de la solución superior enterrada bajo la popularidad de la primera solución. Muy a menudo, la última fila estará vinculada a un tipo de vista separado que sirve como un banner "Cargando ...". Cuando este tipo de vista recibe una llamada de enlace, configurará una devolución de llamada que activará una notificación de cambio de datos cuando haya más datos disponibles. La respuesta aceptada de conectar el oyente de desplazamiento es un desperdicio y probablemente no funcione para otros tipos de diseño.
Michael Hays
3
@mcwise solo llamaría sin fin si el usuario se desplaza activamente hacia atrás y vuelve al último elemento. onBindViewHolder solo se llama una vez cada vez que la vista ingresa a la pantalla y no lo llama constantemente.
David Liu
18

Mi respuesta es una versión modificada de Noor. Pasé de un ListViewlugar que tenía EndlessScrollListener(que puedes encontrar fácilmente en muchas respuestas en SO) a un, RecyclerViewasí que quería unEndlessRecyclScrollListener actualizar fácilmente mi oyente anterior.

Así que aquí está el código, espero que ayude:

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
fuente
1
Su implementación requiere que RecyclerView use LinearLeayoutManager. ¡Mala idea!
Orri
@Orri ¿Podría explicar por qué? ¡Gracias!
Federico Ponzi
1
En su onScrolled, lanza el LayoutManager desde RecyclerView a LinearLayoutManager. No funciona para StaggredGridLayout o cualquier otra cosa. No hay findFirstVisibleItemPosition () en StaggredGridLayout.
Orri
@Orri ¡Gracias por señalar eso! Pero no creo que sea una mala idea, ya que funcionó para mí y, según los votos a favor, otros casos :)
Federico Ponzi
10

Para mí es muy 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;
            }
        }
    });

Tenga cuidado con los trabajos asincrónicos: el mLoading debe cambiarse al final de los trabajos asincrónicos. ¡Espero que sea de ayuda!

Minh Nguyen
fuente
Encontré esta respuesta después de días de cuestionarme, básicamente. Esta es la mejor solución, imo.
wsgeorge
por favor, dar alguna solución para la vista de reciclaje con el número de página de loadmore pasando Android
Harsha
getWebServiceData (); mStoresAdapterData.setOnLoadMoreListener (new StoresAdapter.OnLoadMoreListener () {@Override public void onLoadMore () {// add null, por lo que el adaptador verificará view_type y mostrará la barra de progreso en la parte inferior storesList.add (null); mStoresAdapterData.size.temify. () - 1); ++ número de página; getWebServiceData ();}}); ambos llamando uno tras otro, finalmente solo se pueden ver los datos de la segunda página, por favor ayuda
Harsha
Intenté muchas implementaciones de la paginación de recyclerView pero ninguna funcionó, excepto esta, usted tiene mi
voto a favor,
9

La mayoría de las respuestas suponen los RecyclerViewusos a LinearLayoutManager, o GridLayoutManager, o incluso StaggeredGridLayoutManager, o suponen que el desplazamiento es vertical u horyzontal, pero nadie ha publicado una respuesta completamente genérica .

ViewHolderClaramente, usar el adaptador no es una buena solución. Un adaptador puede tener más de 1 RecyclerViewusándolo. "Adapta" sus contenidos. Debería ser la Vista de reciclador (que es la clase responsable de lo que se muestra actualmente al usuario, y no el adaptador que es responsable solo de proporcionar contenido RecyclerView) lo que debe notificar a su sistema que se necesitan más elementos (para carga).

Aquí está mi solución, usando nada más que las clases abstraídas de RecyclerView (RecycerView.LayoutManager y 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
fuente
1
Su respuesta es correcta y funcionó para todos LayoutManagersmenos un momento: mueva su código de cálculo en otro método scrollListener, muévalo a su onScrollStateChangedlugar onScrolled. Y en onScrollStateChangedsolo verificar:if(newState == RecyclerView.SCROLL_STATE_IDLE) { // calculation code }
Trancer
Gracias por tu consejo, pero no creo que sea una mejor solución. El desarrollador quiere que su escucha se active incluso antes de que la vista del reciclador deje de desplazarse. Por lo tanto, puede terminar de cargar nuevos elementos de datos (si los datos se almacenan localmente), o incluso agregar un titular de vista de barra de progreso al final de la vista del reciclador, de modo que cuando la vista del reciclador se detenga para desplazarse, puede estar en un progreso circular -bar como último elemento visible.
Yairopro
6

Aunque la respuesta aceptada funciona perfectamente, la solución a continuación utiliza addOnScrollListener ya que setOnScrollListener está en desuso y reduce el número de variables y, si las condiciones.

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
fuente
1
A veces mi método loadMore () se llama tres veces en onScrolled (), cualquier idea de por qué se llama tres veces y cómo dejar de llamarlo varias veces.
Ved
dy> 0 siempre es falso
Dushyant Suthar
@ved Eso se debe a que el desplazamiento tiene 3 estados: desplazamiento, desplazamiento, inactivo. Elige uno que sea adecuado.
TheRealChx101
5

Aunque hay tantas respuestas a la pregunta, me gustaría compartir nuestra experiencia de crear la vista de lista sin fin. Recientemente implementamos Carousel LayoutManager personalizado que puede funcionar en el ciclo desplazando la lista infinitamente y hasta cierto punto. Aquí hay una descripción detallada sobre GitHub .

Le sugiero que eche un vistazo a este artículo con recomendaciones breves pero valiosas sobre la creación de LayoutManagers personalizados: http://cases.azoft.com/create-custom-layoutmanager-android/

Vladimir Tchernitski
fuente
5

Con el poder de las funciones de extensión de Kotlin, el código puede verse mucho más elegante. Ponga esto donde quiera (lo tengo dentro de un archivo 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
                }
            }
        }
    })
}

Y luego úsalo así:

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

Esta solución se basa en la respuesta de Kushal Sharma. Sin embargo, esto es un poco mejor porque:

  1. Utiliza en onScrollStateChangedlugar de onScroll. Esto es mejor porque onScrollse llama cada vez que hay algún tipo de movimiento en RecyclerView, mientras onScrollStateChangedque solo se llama cuando se cambia el estado de RecyclerView. El uso onScrollStateChangedle ahorrará tiempo de CPU y, como consecuencia, batería.
  2. Dado que esto utiliza funciones de extensión, esto se puede usar en cualquier RecyclerView que tenga. El código del cliente es solo 1 línea.
Tiago
fuente
Cuando hice Pull SwipeRefreshLayout's setOnRefreshListener the addOnScrolledToEnd is not working en la clase interna privada PullToRefresh: SwipeRefreshLayout.OnRefreshListener {override fun onRefresh () {pbViewMore !!. Visibilidad = View.GONE pageCount = 0 courseList.clear () // llamando a asynctask aquí srlNoMessageRefreshLayout !!. IsRefout! ! .isRefreshing = false}}
Naveen Kumar M
5

Así es como lo hago, simple y breve:

    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
fuente
3

OK, lo hice usando el método onBindViewHolder de RecyclerView.Adapter.

Adaptador:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

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

    ...

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

Fragmento (o Actividad) :

@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
fuente
1
confiar en onBind no es una muy buena idea. RecyclerView hace vistas de caché + tiene algunos administradores de diseño tiene medios para almacenar previamente las cosas en caché Sugeriría configurar un oyente de desplazamiento y cuando el oyente de desplazamiento se detenga, obtenga la última posición visible del elemento desde el administrador de diseño. Si está utilizando administradores de diseño predeterminados, todos tienen métodos findLastVisible **.
yigit
@yigit ¿A qué distancia RecyclerView intentará precachear cosas? Si tiene 500 artículos, realmente dudo que se guarde en caché tan lejos (de lo contrario, será una pérdida enorme de rendimiento). Cuando realmente comenzará a almacenarse en caché (incluso cuando tengamos 480 elementos) eso significa que es hora de cargar otro lote de todos modos.
Alex Orlov
1
No moja las cosas de PreCache a menos que sea un desplazamiento suave a una posición lejana. En ese caso, LLM (por defecto) distribuye dos páginas en lugar de una para que pueda encontrar la vista de destino y desacelerarla. Hace vistas de caché que salen de los límites. De forma predeterminada, este tamaño de caché es 2 y se puede configurar a través de setItemCacheSize.
yigit
1
@yigit solo pensando en el problema No veo cómo el almacenamiento en caché podría presentar un problema aquí; ¡solo estamos interesados ​​en la primera vez que los "últimos" artículos están vinculados de todos modos! Perder la llamada a onBindViewHolder cuando el administrador de diseño reutiliza una vista en caché que ya estaba enlazada no es un problema cuando estamos interesados ​​en buscar más datos al final de la lista. ¿Correcto?
Greg Ennis
2
En mi biblioteca FlexibleAdapter elegí adoptar el desplazamiento sin fin con la llamada entrante onBindViewHolder(), funciona de maravilla. La implementación es pequeña en comparación con el oyente de desplazamiento y es más fácil de usar.
Davideas
3

Mi forma de detectar el evento de carga no es detectar el desplazamiento, sino escuchar si se adjuntó la última vista. Si se adjuntó la última vista, considero que es el momento de cargar más contenido.

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
fuente
2

Intentaría extender el método usado LayoutManager(por ejemplo LinearLayoutManager) y anular scrollVerticallyBy(). En primer lugar, llamaría superprimero y luego verificaría el valor entero devuelto. Si el valor es igual a 0entonces se alcanza un borde inferior o superior. Entonces usaríafindLastVisibleItemPosition() método para averiguar qué borde se alcanza y cargar más datos si es necesario. Solo una idea.

Además, incluso puede devolver su valor de ese método permitiendo un desplazamiento excesivo y mostrando el indicador de "carga".

sergej shafarenka
fuente
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
fuente
1
onScrollStateChanged()no funcionará, ya que solo se llamará cuando cambie el estado del desplazamiento y no mientras esté desplazándose. Debes usar el onScrolled()método.
mradzinski
2

Logré una implementación de tipo de desplazamiento infinito usando esta lógica en el onBindViewHoldermétodo de mi RecyclerView.Adapterclase.

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

Con este código, cuando RecyclerView llega al último elemento, incrementa la página actual y las devoluciones de llamada en una interfaz que es responsable de cargar más datos de la API y agregar los nuevos resultados al adaptador.

¿Puedo publicar un ejemplo más completo si esto no está claro?

speedynomads
fuente
Sé que esta publicación es muy antigua, pero soy algo nuevo y no sé dónde obtener las variables que está comparando. Supongo que mCurrentPage y mTotalPageCount se refieren a su conjunto de datos, y mItems es la lista de elementos de la lista, pero ¿cómo encuentro la posición?
BooleanCheese
Puede ver cómo implementé mi RecyclerView.Adapter aquí: github.com/dominicthomas/FlikrGridRecyclerView/blob/master/app/…
speedynomads
2

Para personas que usan StaggeredGridLayoutManager aquí está mi implementación, funciona para mí.

 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
fuente
@SudheeshMohan Aquí está la clase completa, es pequeña) En mi proyecto cambio dinámicamente el recuento de
períodos
recyclerView.canScrollVertically (1) lo hará pero requiere API 14. :(
Sudheesh Mohan
2

Hay una biblioteca fácil de usar para este paginado llamado . Admite ListView y RecyclerView (LinearLayout, GridLayout y StaggeredGridLayout).

Aquí está el enlace al proyecto en Github

nonybrighto
fuente
2
  1. Crea una clase abstracta y extiende 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 actividad (o fragmento) agregue addOnScrollListener a recyclerView

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

Tengo un ejemplo bastante detallado de cómo paginar con un RecyclerView. En un nivel alto, tengo un PAGE_SIZE establecido, digamos 30. Entonces solicito 30 elementos y si obtengo 30, solicito la siguiente página. Si obtengo menos de 30 elementos, señalizo una variable para indicar que se ha alcanzado la última página y luego dejo de solicitar más páginas. Compruébalo y hazme saber lo que piensas.

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

toobsco42
fuente
1

Aquí mi solución usando AsyncListUtil , en la web dice: Tenga en cuenta que esta clase usa un solo subproceso para cargar los datos, por lo que es adecuado para cargar datos del almacenamiento secundario, como el disco, pero no de la red. pero estoy usando odata para leer los datos y trabajar bien. Echo de menos en mi ejemplo entidades de datos y métodos de red. Incluyo solo el adaptador de ejemplo.

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
fuente
1

@kushal @abdulaziz

¿Por qué no usar esta lógica en su lugar?

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
fuente
Mi onScroller () llamó tres veces para un solo elemento, así que obtengo end_of_list tres veces y mi lógica de paginación se llama tres veces. Cómo corregir esto para llamar a la paginación solo una vez.
Ved
@ved Eso no se supone que suceda. Ese código anterior asegura que la end_of_listcondición solo se llamará una vez. El indicador _isLastItemes una variable global en la actividad que tiene un valor predeterminado de false. Lo que hice fue ejecutar una tarea en segundo plano después de detectar el final de la lista, recuperar la página siguiente y luego notificar al adaptador del nuevo conjunto de datos, y finalmente configurarlo _isLastItemen falso nuevamente.
Leonapse
1

Prueba a continuación:

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
fuente
1

He creado LoadMoreRecyclerView usando 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");
        }
    }
}

// Añádelo en xml como

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

OnCreate o 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
fuente
1

También es posible implementar sin el oyente de desplazamiento, utilizando solo la lógica pura del modelo de datos. La vista de desplazamiento requiere obtener elementos por posición, así como el recuento máximo de elementos. El modelo puede tener la lógica de fondo para buscar los elementos necesarios en fragmentos, en lugar de uno por uno, y hacer esto en el hilo de fondo, notificando la vista cuando los datos estén listos.

Este enfoque permite tener la cola de recuperación que prefiere los elementos solicitados más recientemente (por lo que actualmente son visibles) sobre los envíos más antiguos (lo más probable es que ya estén desplazados), controle el número de subprocesos paralelos para usar y cosas por el estilo. El código fuente completo para este enfoque (aplicación de demostración y biblioteca reutilizable) está disponible aquí .

Audrius Meskauskas
fuente
0

Hay un método public void setOnScrollListener (RecyclerView.OnScrollListener listener)en https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29 . Usa eso

EDITAR:

Anular onScrollStateChangedmétodo dentro de onScrollListenery hacer esto

            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
            }
Pantera
fuente
1
¡Por favor sé más específico! ¿Cómo uso RecyclerView.OnScrollListener para determinar que el usuario se desplazó al final de la lista?
erdna
No hay onScroll en RecyclerView.OnScrollListener ! Mezcla AbsListView.OnScrollListener con RecyclerView.OnScrollListener.
erdna
ya se supone que debe seronScrollStateChanged
Panther
onScrollStateChangedno funcionará, ya que solo se llamará cuando cambie el estado del desplazamiento y no mientras esté desplazándose.
Pedro Oliveira
@PedroOliveira ¿Es posible onScrollStateChangeddeterminar que el usuario se desplaza al último elemento?
erdna
0

Verifique que todo se explica en detalle: Paginación usando RecyclerView de la A a la 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();
}

en 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
fuente
El enlace puede contener una respuesta, pero trate de explicar algunas partes sustanciales de las explicaciones en el enlace de su publicación.
Ian
0

Ninguna de estas respuestas tiene en cuenta si la lista es demasiado pequeña o no.

Aquí hay un código que he estado usando que funciona en RecycleViews en ambas direcciones.

@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
fuente
0

Te dejo mi aproximación. Funciona bien para mi.

Espero que te ayude.

/**
 * 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
fuente
0

Esta solución funciona perfectamente para mí.

//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
fuente