¿Cómo saber si un RecyclerView / LinearLayoutManager se desplaza hacia arriba o hacia abajo?

81

Actualmente estoy usando el siguiente código para verificar si SwipeRefreshLayout debería estar habilitado.

private void laySwipeToggle() {
    if (mRecyclerView.getChildCount() == 0 || mRecyclerView.getChildAt(0).getTop() == 0) {
        mLaySwipe.setEnabled(true);
    } else {
        mLaySwipe.setEnabled(false);
    }
}

Pero aquí está el problema. Cuando se desplaza al límite de la vista de otro elemento, mRecyclerView.getChildAt(0).getTop()también devuelve 0.

El problema

¿Hay algo como RecyclerView.isScrolledToBottom()o RecyclerView.isScrolledToTop()?

EDITAR: (mRecyclerView.getChildAt(0).getTop() == 0 && linearLayoutManager.findFirstVisibleItemPosition() == 0)algo así como RecyclerView.isScrolledToTop(), pero ¿de qué RecyclerView.isScrolledToBottom()?

Saren Arterius
fuente
Supongo que esto último se puede lograr verificando la parte inferior de la vista de reciclaje con el último hijo, es decir, algo en las líneas de mRecyclerView.getBottom () == linearLayoutmanager.findViewbyPosition (adapter.getItemCount () - 1) .getBottom ()
humblerookie
@Saren Arterius, es posible que desee echar un vistazo a esto
Sheraz Ahmad Khilji

Respuestas:

109

La solución está en el administrador de diseño.

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

// Add this to your Recycler view
recyclerView.setLayoutManager(layoutManager);

// To check if at the top of recycler view
if(layoutManager.firstCompletelyVisibleItemPosition()==0){
    // Its at top
}

// To check if at the bottom of recycler view
if(layoutManager.lastCompletelyVisibleItemPosition()==data.size()-1){
    // Its at bottom
}

EDITAR

En caso de que el tamaño de su artículo sea más grande que la pantalla, utilice lo siguiente para detectar el evento principal.

RecyclerView recyclerView = (RecyclerView) view;
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

int pos = linearLayoutManager.findFirstVisibleItemPosition();

if(linearLayoutManager.findViewByPosition(pos).getTop()==0 && pos==0){
    return true;
}

PD: En realidad, si coloca el RecyclerViewdirectamente dentro del SwipeRefreshview, no necesitaría hacer esto

humblerookie
fuente
No, todavía sucede cuando RecyclerView es el hijo directo de SwipeRefreshLayout. Pero comprobar la firstCompletelyVisibleItemPosition soluciona el problema. Necesito señalar algo aquí. Si ninguno de los elementos es completamente visible, firstCompletelyVisibleItemPosition () devuelve -1. Por lo tanto, esta solución no funcionará si el primer elemento no encaja en RecyclerView cuando se desplaza hacia arriba. Pero es una situación muy rara.
eluleci
1
@eluleci: Verifique si la lista está en la parte superior de la vista RecyclerView rc = (RecyclerView); LinearLayoutManager lm = (LinearLayoutManager) rc.getLayoutManager (); if (lm.findViewByPosition (lm.findFirstVisibleItemPosition ()). getTop () == 0 && lm.findFirstVisibleItemPosition () == 0) devuelve verdadero; // Esto funcionaría evn si el elemento es de un tamaño más grande que la pantalla
humblerookie
2
Esto fallaría si el elemento (primero o último) es más grande que toda la pantalla. Porque, first/lastCompletelyVisibleItemPosition()devolverá -1, implica que no hay ningún elemento completamente visible.
Subin Sebastian
4
Ambos lastVisibleItemPositiony lastCompletelyVisibleItemPositionno funcionan. (no se puede resolver el método). ¿Ayuda?
Stardust
4
findLastCompletelyVisibleItemPositionFALLARÁ si la altura del último elemento es mayor que la altura de la pantalla. Significa que el último elemento es visible, pero no completamente visible, entonces layout.findLastCompletelyVisibleItemPosition()=-1(RecyclerView.NO_POSITION). Usar lastVisibleItemPositiony verificar la parte inferior de la última vista en el diseño será más seguro.
desapareció el
78

Puede intentarlo recyclerView.canScrollVertically(int direction), si solo necesita saber si es posible desplazarse o no.

Enteros de dirección:

  • -1 para arriba
  • 1 por abajo
  • 0 siempre devolverá falso.
Penzzz
fuente
Sin embargo, esto tendría que repetirse en un intervalo, ¿verdad?
Stardust
1
Puede publicar esto en recyclerView.addOnScrollListener ... funciona como un encanto ... gracias
therealprashant
2
¡Excelente! Esto devuelve un falso cuando llegamos al final del desplazamiento, necesitaba este evento.
Box
1
Excelente. Use esto en onScrollStateChangedobras para mí.
Lym Zoy
5
enteros de dirección: -1 para arriba, 1 para abajo, 0 siempre devolverá falso.
Grux
33

Para comprobar si RecyclerViewse desplaza hacia abajo. Utilice el siguiente código.

/**
     * Check whether the last item in RecyclerView is being displayed or not
     *
     * @param recyclerView which you would like to check
     * @return true if last position was Visible and false Otherwise
     */
    private boolean isLastItemDisplaying(RecyclerView recyclerView) {
        if (recyclerView.getAdapter().getItemCount() != 0) {
            int lastVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();
            if (lastVisibleItemPosition != RecyclerView.NO_POSITION && lastVisibleItemPosition == recyclerView.getAdapter().getItemCount() - 1)
                return true;
        }
        return false;
    }

Alguna información adicional

Si se desea implementar ScrollToBottomen RecyclerViewcuando Edittextes tappedentonces le recomiendo que añadir retardo de 1 segundo de esta manera:

 edittext.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_UP)
                    if (isLastItemDisplaying(recyclerView)) {
// The scrolling can happen instantly before keyboard even opens up so to handle that we add 1 second delay to scrolling
                        recyclerView.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount() - 1);

                            }
                        }, 1000);
                    }
                return false;
            }
        });
Sheraz Ahmad Khilji
fuente
Trabajó para mi. He tenido que añadir a la RecyclerView addOnScrollListener () aunque
aphoe
1
esto no funciona para el caso trivial en el que hay un elemento en la vista de reciclaje y es completamente visible.
techtinkerer
salvaste un día
Reena
22

@Saren Arterius, utilizando el addOnScrollListener de RecycleView , puede encontrar la parte superior o inferior de desplazamiento vertical RecycleView como se muestra a continuación,

RecyclerView rv = (RecyclerView)findViewById(R.id.rv);

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

                if (dy < 0) {
                    // Recycle view scrolling up...

                } else if (dy > 0) {
                    // Recycle view scrolling down...
                }
            }
        });
TejaDroid
fuente
6

Use recyclerView.canScrollVertically (dirección int) para verificar si se alcanzó la parte superior o inferior del desplazamiento.

dirección = 1 para desplazarse hacia abajo (abajo)

dirección = -1 para desplazarse hacia arriba (arriba)

si el método devuelve falso, eso significa que alcanzó la parte superior o inferior depende de la dirección.

kartik srivastava
fuente
¡Muchas gracias funcionó!
Ferer Atlus
5

Simplemente mantenga una referencia a su layoutManager y configure onScrollListener en su vista de reciclador de esta manera

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();
        firstVisibleItemIndex = mLayoutManager.findFirstVisibleItemPosition();

        //synchronizew loading state when item count changes
        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading)
            if ((totalItemCount - visibleItemCount) <= firstVisibleItemIndex) {
                // Loading NOT in progress and end of list has been reached
                // also triggered if not enough items to fill the screen
                // if you start loading
                loading = true;
            } else if (firstVisibleItemIndex == 0){
                // top of list reached
                // if you start loading
                loading = true;
            }
        }
    }
});
SGal
fuente
1
setOnScrollListnerahora está en desuso.
Shajeel Afzal
2
Debe usar addOnScrollListenerporque como dijo @shajeelAfzal setOnScrollListenerahora está en desuso, debe usar addOnScrollListenerporque como dijo @shajeelAfzal setOnScrollListenerahora está en desuso, verifique aquí
Vasilisfoo
¿Qué es previousTotal?
CoolMind
3

Puede detectar la parte superior utilizando lo siguiente

 recyclerView.addOnScrollListener(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) {
            super.onScrollStateChanged(recyclerView, newState);
            if(IsRecyclerViewAtTop())
            {
                //your recycler view reached Top do some thing
             }
        }
    });

  private boolean IsRecyclerViewAtTop()   {
        if(recyclerView.getChildCount() == 0)
            return true;
        return recyclerView.getChildAt(0).getTop() == 0;
    }

Esto detectará la parte superior cuando suelte el dedo una vez que haya alcanzado la parte superior, si desea detectar tan pronto como el reacyclerview alcance la parte superior, compruebe el método if(IsRecyclerViewAtTop())internoonScrolled

Manohar Reddy
fuente
3
Esto parece estar mal. getChildAt()devuelve el primer hijo de ViewGroup, que podría (y será) diferente del primer elemento de la lista. Si ese niño está perfectamente alineado con la parte superior de la vista de reciclaje, su IsRecyclerViewAtTop()método devolverá verdadero, aunque no esté en la parte superior.
aspyct
2

He escrito un RecyclerViewHelper para saber que la vista de reciclaje está arriba o abajo.

public class RecyclerViewHelper {
public static boolean isAtTop(RecyclerView recyclerView) {
    if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        return isAtTopBeforeIceCream(recyclerView);
    } else {
        return !ViewCompat.canScrollVertically(recyclerView, -1);
    }
}

private static boolean isAtTopBeforeIceCream(RecyclerView recyclerView) {
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    if (layoutManager instanceof LinearLayoutManager) {
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
        int pos = linearLayoutManager.findFirstVisibleItemPosition();
        if (linearLayoutManager.findViewByPosition(pos).getTop() == recyclerView.getPaddingTop() && pos == 0)
            return true;
    }
    return false;
}


public static boolean isAtBottom(RecyclerView recyclerView) {
    if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        return isAtBottomBeforeIceCream(recyclerView);
    } else {
        return !ViewCompat.canScrollVertically(recyclerView, 1);
    }
}

private static boolean isAtBottomBeforeIceCream(RecyclerView recyclerView) {
    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    int count = recyclerView.getAdapter().getItemCount();
    if (layoutManager instanceof LinearLayoutManager) {
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
        int pos = linearLayoutManager.findLastVisibleItemPosition();
        int lastChildBottom = linearLayoutManager.findViewByPosition(pos).getBottom();
        if (lastChildBottom == recyclerView.getHeight() - recyclerView.getPaddingBottom() && pos == count - 1)
            return true;
    }
    return false;
}

}

chefish
fuente
2

puedes hacer esto, es un trabajo para mí

      mRecycleView.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 topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ?
                        0 : recyclerView.getChildAt(0).getTop();
                LinearLayoutManager linearLayoutManager1 = (LinearLayoutManager) recyclerView.getLayoutManager();
                int firstVisibleItem = linearLayoutManager1.findFirstVisibleItemPosition();
                swipeRefreshLayout.setEnabled(firstVisibleItem == 0 && topRowVerticalPosition >= 0);
            }
        });
Amal Kronz
fuente
2

Para saber si el RecyclerView se desplaza hacia abajo, puede reutilizar los métodos utilizados para la barra de desplazamiento.

Este es el cálculo, escrito por conveniencia como una función de extensión de Kotlin:

fun RecyclerView.isScrolledToBottom(): Boolean {
    val contentHeight = height - (paddingTop + paddingBottom)
    return computeVerticalScrollRange() == computeVerticalScrollOffset() + contentHeight
}
Giso Bartels
fuente
¡Buena solución! Vi algunos problemas en los que el tamaño del desplazamiento era de solo 1, aunque me desplacé en la parte inferior (probablemente algunos problemas de redondeo) Así que cambié el código para usar un margen:fun RecyclerView.isScrolledToBottom(margin: Int = 1): Boolean { val contentHeight = height - (paddingTop + paddingBottom) return computeVerticalScrollRange() - computeVerticalScrollOffset() - contentHeight >= margin }
Renso Lohuis
Quiero decir, menor o igual al margen. Así:fun RecyclerView.isScrolledToBottom(margin: Int = 1): Boolean { val contentHeight = height - (paddingTop + paddingBottom) return computeVerticalScrollRange() - computeVerticalScrollOffset() - contentHeight <= margin }
Renso Lohuis
1

puedes probar con OnTouchListener:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_UP
            || e.getAction() == MotionEvent.ACTION_MOVE){
        if (mLinearLayoutManager.findFirstCompletelyVisibleItemPosition() > 0)
        {
        // beginning of the recycler 
        }

        if (mLinearLayoutManager.findLastCompletelyVisibleItemPosition()+1 < recyclerView.getAdapter().getItemCount())
        {
        // end of the recycler 
        }         
     }             
return false;
}
Jch Pal
fuente