Detecta cuando RecyclerView alcanza la posición más baja mientras se desplaza

95

Tengo este código para un RecyclerView.

    recyclerView = (RecyclerView)rootview.findViewById(R.id.fabric_recyclerView);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.addItemDecoration(new RV_Item_Spacing(5));
    FabricAdapter fabricAdapter=new FabricAdapter(ViewAdsCollection.getFabricAdsDetailsAsArray());
    recyclerView.setAdapter(fabricAdapter);

Necesito saber cuándo el RecyclerView alcanza la posición más inferior mientras me desplazo. Es posible ? Si es así, ¿cómo?

Adrian
fuente
1
reciclarView.canScrollVertically (dirección int); ¿Cuál debería ser el parámetro así que tenemos que pasar aquí?
Adrian
@Adrian, en caso de que todavía esté interesado en esta pregunta :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Respuestas:

179

también hay una forma sencilla de hacerlo

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

        if (!recyclerView.canScrollVertically(1)) {
            Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();

        }
    }
});

enteros de dirección: -1 para arriba, 1 para abajo, 0 siempre devolverá falso.

Kaustubh Bhagwat
fuente
17
onScrolled () evita que la detección ocurra dos veces.
Ian Wambai
No puedo agregar ScrollListnerEvent a My RecyclerView. El código de onScrollStateChanged no se ejecuta.
Sandeep Yohans
1
si solo desea que esto se active una vez (muestre Toast una vez) agregue la bandera booleana (una variable de miembro de la clase) a la declaración if y if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
configúrela
@ bastami82 uso el truco que sugieres para evitar la impresión doble de tostadas, pero no funciona
Vishwa Pratap
4
Agregar newState == RecyclerView.SCROLL_STATE_IDLEen la ifdeclaración funcionaría.
Lee Chun Hoe
58

Utilice este código para evitar llamadas repetidas

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

            if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
                Log.d("-----","end");
                
            }
        }
    });
Milind Chaudhary
fuente
2
Intenté responder, pero esto es simple y perfecto para mí. gracias
Vishwa Pratap
@Venkatesh Bienvenido.
Milind Chaudhary
La única respuesta que no me dio ningún otro error
The Fluffy T Rex
@TheFluffyTRex Gracias
Milind Chaudhary
46

Simplemente implemente un addOnScrollListener () en su vista de reciclaje. Luego, dentro del oyente de desplazamiento, implemente el siguiente código.

RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if (mIsLoading)
                return;
            int visibleItemCount = mLayoutManager.getChildCount();
            int totalItemCount = mLayoutManager.getItemCount();
            int pastVisibleItems = mLayoutManager.findFirstVisibleItemPosition();
            if (pastVisibleItems + visibleItemCount >= totalItemCount) {
                //End of list
            }
        }
    };
Febi M Felix
fuente
1
¿Puede explicarnos un poco sobre mIsLoading, por favor? ¿Dónde lo estás configurando o cambiando el valor?
MiguelHincapieC
mLoading es solo una variable booleana que indica si la vista está activada o no. Por ejemplo; si la aplicación está completando la vista de reciclaje, mLoading será verdadero y se volverá falso una vez que se complete la lista.
Febi M Felix
1
esta solución no funciona correctamente. eche un vistazo a stackoverflow.com/a/48514857/6674369
Andriy Antonov
3
No se puede resolver el método 'findFirstVisibleItemPosition'enandroid.support.v7.widget.RecyclerView
Iman Marashi
2
@Iman Marashi, es necesario reparto: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Este trabajo.
bitvale
16

La respuesta está en Kotlin, funcionará en Java. IntelliJ debería convertirlo por usted si copia y pega.

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

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

        // 3 lines below are not needed.
        Log.d("TAG","Last visible item is: ${gridLayoutManager.findLastVisibleItemPosition()}")
        Log.d("TAG","Item count is: ${gridLayoutManager.itemCount}")
        Log.d("TAG","end? : ${gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1}")

        if(gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1){
            // We have reached the end of the recycler view.
        }

        super.onScrolled(recyclerView, dx, dy)
    }
})

Esto también funcionará LinearLayoutManagerporque tiene los mismos métodos utilizados anteriormente. A saber findLastVisibleItemPosition()y getItemCount()( itemCounten Kotlin).

daka
fuente
6
Buen enfoque, pero un problema son las declaraciones en if loop se ejecutará varias veces
Vishnu
si hubiera obtenido la solución para los mismos problemas
Vishwa Pratap
1
@Vishnu Una simple variable booleana puede arreglar eso.
Daka
8

No estaba obteniendo una solución perfecta con las respuestas anteriores porque se activaba dos veces incluso en onScrolled

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
           if( !recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN))
               context?.toast("Scroll end reached")
        }
Manoj Perumarath
fuente
1
Pero la recyclerView.canScrollVertically(direction)dirección es cualquier número entero + vs, por lo que puede 4 si está en la parte inferior o -12 si está en la parte superior.
Xenolion
5

Prueba esto

He usado las respuestas anteriores, se ejecuta siempre cuando irá al final de la vista del reciclador,

¿Si desea comprobar solo una vez si está en un fondo o no? Ejemplo: - Si tengo la lista de 10 elementos cada vez que voy en la parte inferior, me mostrará y nuevamente si me desplazo de arriba a abajo no se imprimirá nuevamente, y si agrega más listas y va allí, se mostrará nuevamente.

Nota: - Utilice este método cuando se ocupe de la compensación al presionar API

  1. Cree una clase llamada EndlessRecyclerViewScrollListener

        import android.support.v7.widget.GridLayoutManager;
        import android.support.v7.widget.LinearLayoutManager;
        import android.support.v7.widget.RecyclerView;
        import android.support.v7.widget.StaggeredGridLayoutManager;
    
        public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
            // The minimum amount of items to have below your current scroll position
            // before loading more.
            private int visibleThreshold = 5;
            // The current offset index of data you have loaded
            private int currentPage = 0;
            // The total number of items in the dataset after the last load
            private int previousTotalItemCount = 0;
            // True if we are still waiting for the last set of data to load.
            private boolean loading = true;
            // Sets the starting page index
            private int startingPageIndex = 0;
    
            RecyclerView.LayoutManager mLayoutManager;
    
            public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
                this.mLayoutManager = layoutManager;
            }
    
        //    public EndlessRecyclerViewScrollListener() {
        //        this.mLayoutManager = layoutManager;
        //        visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
        //    }
    
            public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
                this.mLayoutManager = layoutManager;
                visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
            }
    
            public int getLastVisibleItem(int[] lastVisibleItemPositions) {
                int maxSize = 0;
                for (int i = 0; i < lastVisibleItemPositions.length; i++) {
                    if (i == 0) {
                        maxSize = lastVisibleItemPositions[i];
                    }
                    else if (lastVisibleItemPositions[i] > maxSize) {
                        maxSize = lastVisibleItemPositions[i];
                    }
                }
                return maxSize;
            }
    
            // This happens many times a second during a scroll, so be wary of the code you place here.
            // We are given a few useful parameters to help us work out if we need to load some more data,
            // but first we check if we are waiting for the previous load to finish.
            @Override
            public void onScrolled(RecyclerView view, int dx, int dy) {
                int lastVisibleItemPosition = 0;
                int totalItemCount = mLayoutManager.getItemCount();
    
                if (mLayoutManager instanceof StaggeredGridLayoutManager) {
                    int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
                    // get maximum element within the list
                    lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
                } else if (mLayoutManager instanceof GridLayoutManager) {
                    lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                } else if (mLayoutManager instanceof LinearLayoutManager) {
                    lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                }
    
                // 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;
                }
    
                // 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.
                // threshold should reflect how many total columns there are too
                if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
                    currentPage++;
                    onLoadMore(currentPage, totalItemCount, view);
                    loading = true;
                }
            }
    
            // Call this method whenever performing new searches
            public void resetState() {
                this.currentPage = this.startingPageIndex;
                this.previousTotalItemCount = 0;
                this.loading = true;
            }
    
            // Defines the process for actually loading more data based on page
            public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
    
        }
    
  2. usa esta clase como esta

         LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
            recyclerView.setLayoutManager(linearLayoutManager);
            recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener( linearLayoutManager) {
                @Override
                public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
                    Toast.makeText(getActivity(),"LAst",Toast.LENGTH_LONG).show();
                }
            });
    

Está funcionando perfecto al final, comuníquese conmigo si tiene algún problema

Sunil
fuente
Esto fue muy útil :)
Devrath
4

Ahí está mi implementación, es muy útil para StaggeredGridLayout.

Uso:

private EndlessScrollListener scrollListener =
        new EndlessScrollListener(new EndlessScrollListener.RefreshList() {
            @Override public void onRefresh(int pageNumber) {
                //end of the list
            }
        });

rvMain.addOnScrollListener(scrollListener);

Implementación del oyente:

class EndlessScrollListener extends RecyclerView.OnScrollListener {
private boolean isLoading;
private boolean hasMorePages;
private int pageNumber = 0;
private RefreshList refreshList;
private boolean isRefreshing;
private int pastVisibleItems;

EndlessScrollListener(RefreshList refreshList) {
    this.isLoading = false;
    this.hasMorePages = true;
    this.refreshList = refreshList;
}

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

    int visibleItemCount = manager.getChildCount();
    int totalItemCount = manager.getItemCount();
    int[] firstVisibleItems = manager.findFirstVisibleItemPositions(null);
    if (firstVisibleItems != null && firstVisibleItems.length > 0) {
        pastVisibleItems = firstVisibleItems[0];
    }

    if (visibleItemCount + pastVisibleItems >= totalItemCount && !isLoading) {
        isLoading = true;
        if (hasMorePages && !isRefreshing) {
            isRefreshing = true;
            new Handler().postDelayed(new Runnable() {
                @Override public void run() {
                    refreshList.onRefresh(pageNumber);
                }
            }, 200);
        }
    } else {
        isLoading = false;
    }
}

public void noMorePages() {
    this.hasMorePages = false;
}

void notifyMorePages() {
    isRefreshing = false;
    pageNumber = pageNumber + 1;
}

interface RefreshList {
    void onRefresh(int pageNumber);
}  }
Aleksey Timoshchenko
fuente
¿Por qué agregar un retraso de 200 milisegundos a la devolución de llamada?
Mani
@Mani No recuerdo exactamente en este momento, pero tal vez lo hice solo para un efecto suave ...
Aleksey Timoshchenko
3

También estaba buscando esta pregunta, pero no encontré la respuesta que me satisficiera, así que creo una realización propia de recyclingView.

otras soluciones son menos precisas que la mía. por ejemplo: si el último elemento es bastante grande (gran cantidad de texto), la devolución de llamada de otras soluciones llegará mucho antes, y luego que el control de reciclaje realmente llegó al final.

mi solución soluciona este problema.

class CustomRecyclerView: RecyclerView{

    abstract class TopAndBottomListener{
        open fun onBottomNow(onBottomNow:Boolean){}
        open fun onTopNow(onTopNow:Boolean){}
    }


    constructor(c:Context):this(c, null)
    constructor(c:Context, attr:AttributeSet?):super(c, attr, 0)
    constructor(c:Context, attr:AttributeSet?, defStyle:Int):super(c, attr, defStyle)


    private var linearLayoutManager:LinearLayoutManager? = null
    private var topAndBottomListener:TopAndBottomListener? = null
    private var onBottomNow = false
    private var onTopNow = false
    private var onBottomTopScrollListener:RecyclerView.OnScrollListener? = null


    fun setTopAndBottomListener(l:TopAndBottomListener?){
        if (l != null){
            checkLayoutManager()

            onBottomTopScrollListener = createBottomAndTopScrollListener()
            addOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = l
        } else {
            removeOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = null
        }
    }

    private fun createBottomAndTopScrollListener() = object :RecyclerView.OnScrollListener(){
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            checkOnTop()
            checkOnBottom()
        }
    }

    private fun checkOnTop(){
        val firstVisible = linearLayoutManager!!.findFirstCompletelyVisibleItemPosition()
        if(firstVisible == 0 || firstVisible == -1 && !canScrollToTop()){
            if (!onTopNow) {
                onTopNow = true
                topAndBottomListener?.onTopNow(true)
            }
        } else if (onTopNow){
            onTopNow = false
            topAndBottomListener?.onTopNow(false)
        }
    }

    private fun checkOnBottom(){
        var lastVisible = linearLayoutManager!!.findLastCompletelyVisibleItemPosition()
        val size = linearLayoutManager!!.itemCount - 1
        if(lastVisible == size || lastVisible == -1 && !canScrollToBottom()){
            if (!onBottomNow){
                onBottomNow = true
                topAndBottomListener?.onBottomNow(true)
            }
        } else if(onBottomNow){
            onBottomNow = false
            topAndBottomListener?.onBottomNow(false)
        }
    }


    private fun checkLayoutManager(){
        if (layoutManager is LinearLayoutManager)
            linearLayoutManager = layoutManager as LinearLayoutManager
        else
            throw Exception("for using this listener, please set LinearLayoutManager")
    }

    private fun canScrollToTop():Boolean = canScrollVertically(-1)
    private fun canScrollToBottom():Boolean = canScrollVertically(1)
}

luego en tu actividad / fragmento:

override fun onCreate() {
    customRecyclerView.layoutManager = LinearLayoutManager(context)
}

override fun onResume() {
    super.onResume()
    customRecyclerView.setTopAndBottomListener(this)
}

override fun onStop() {
    super.onStop()
    customRecyclerView.setTopAndBottomListener(null)
}

espero que ayude a alguien ;-)

Andriy Antonov
fuente
1
¿Qué parte de la solución de otros que no te satisfizo? debe explicarlo primero antes de sugerir su respuesta
Zam Sunk
@ZamSunk porque otras soluciones son menos precisas que la mía. por ejemplo, si el último elemento es bastante pequeño (mucho texto), la devolución de llamada de otras soluciones llegará mucho antes y luego que el objeto RecyclerView realmente llegue al final. mi solución soluciona este problema.
Andriy Antonov
2

Después de no estar satisfecho con la mayoría de las otras respuestas en este hilo, encontré algo que creo que es mejor y no está en ninguna parte aquí.

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (!recyclerView.canScrollVertically(1) && dy > 0)
        {
             //scrolled to bottom
        }else if (!recyclerView.canScrollVertically(-1) && dy < 0)
        {
            //scrolled to bottom
        }
    }
});

Esto es simple y se activará exactamente una vez en todas las condiciones cuando se haya desplazado hacia la parte superior o inferior.

nadie392
fuente
0

Esta es mi solución:

    val onScrollListener = object : RecyclerView.OnScrollListener() {

    override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
        directionDown = dy > 0
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        if (recyclerView.canScrollVertically(1).not()
                && state != State.UPDATING
                && newState == RecyclerView.SCROLL_STATE_IDLE
                && directionDown) {
               state = State.UPDATING
            // TODO do what you want  when you reach bottom, direction
            // is down and flag for non-duplicate execution
        }
    }
}
Alex Zezekalo
fuente
¿de dónde state sacaste esa enumeración?
raditya gumay
El estado es una enumeración mía, esta es una bandera para verificar los estados para esas pantallas (usé MVVM con enlace de datos en mi aplicación)
Alex Zezekalo
¿Cómo implementaría la devolución de llamada si no hay conectividad a Internet?
Aks4125
0

Podemos usar la interfaz para obtener la posición.

Interfaz: crea una interfaz para el oyente

public interface OnTopReachListener { void onTopReached(int position);}

Actividad :

mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() {
@Override
public void onTopReached(int position) {
    Log.i("Position","onTopReached "+position);  
}
});

Adaptador:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
Amuthan S
fuente