Получить видимые элементы в RecyclerView

Мне нужно знать, какие элементы в настоящее время отображаются в моем RecyclerView. Там нет эквивалента OnScrollListener.onScroll(...) метод на ListViews. Я пытался работать с View.getGlobalVisibleRect(...), но этот хак слишком уродлив и не всегда работает.

У кого-нибудь есть идеи?

12 ответов

Решение

Первый / последний видимый ребенок зависит от LayoutManager, Если вы используете LinearLayoutManager или же GridLayoutManager, ты можешь использовать

int findFirstVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

Например:

GridLayoutManager layoutManager = ((GridLayoutManager)mRecyclerView.getLayoutManager());
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();

За LinearLayoutManagerПервый / последний зависит от заказа адаптера. Не запрашивать детей из RecyclerView; LayoutManager может предпочесть макетировать больше элементов, чем видно для кэширования.

Для тех, у кого есть логика, которая будет реализована внутри адаптера RecyclerView, вы все равно можете использовать подход @ernesto в сочетании с on scrollListener, чтобы получить what you wantпо мере обращения к RecyclerView. Внутри адаптера у вас будет что-то вроде этого:

@Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if(manager instanceof LinearLayoutManager && getItemCount() > 0) {
            LinearLayoutManager llm = (LinearLayoutManager) manager;
            recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                }

                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                        int visiblePosition = llm.findFirstCompletelyVisibleItemPosition();
                        if(visiblePosition > -1) {
                            View v = llm.findViewByPosition(visiblePosition);
                            //do something
                            v.setBackgroundColor(Color.parseColor("#777777"));
                        }
                }
            });
        }
    }

Наконец, я нашел решение узнать, является ли текущий элемент видимым из события onBindViewHolder в адаптере.

Ключевым является метод isViewPartiallyVisible из LayoutManager.

В вашем адаптере вы можете получить de LayoutManager из recyclerView, который вы можете получить из события onAttachedToRecyclerView.

Все приведенные выше ответы верны, и я хотел бы добавить также снимок моих рабочих кодов.

recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
           //some code when initially scrollState changes
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //Some code while the list is scrolling
            LinearLayoutManager lManager = (LinearLayoutManager) recycler.getLayoutManager();
            int firstElementPosition = lManager.findFirstVisibleItemPosition();
            
        }
    });

Приложение:

Предложенные функции findLast...Position() не работают правильно в сценарии с сворачивающейся панелью инструментов, когда панель инструментов развернута.

Кажется, что вид рециркулятора имеет фиксированную высоту, и, хотя панель инструментов развернута, рециркулятор перемещается вниз, частично за пределы экрана. Как следствие, результаты предложенных функций слишком высоки. Пример: последний видимый элемент называется #9, но на самом деле элемент #7 является последним на экране.

Это поведение также является причиной, по которой моему представлению часто не удавалось прокрутить до правильной позиции, то есть scrollToPosition() не работал правильно (я окончательно свернул панель инструментов программно).

Ты можешь использовать recyclerView.getChildAt() чтобы получить каждого видимого ребенка, и установить некоторые теги convertview.setTag(index) на этом представлении в коде адаптера поможет вам связать его с данными адаптера.

Следующий Linear / Grid LayoutManager методы могут быть использованы для проверки, какие элементы visible

int findFirstVisibleItemPosition();
int findLastVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

и если вы хотите отслеживать is item visible on screen за некоторый порог вы можете обратиться к следующему блогу.

https://proandroiddev.com/detecting-list-items-perceived-by-user-8f164dfb1d05

За StaggeredGridLayoutManager сделай это:

RecyclerView rv = findViewById(...);
StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(...);
rv.setLayoutManager(lm);

И чтобы увидеть видимые элементы:

int[] viewsIds = lm.findFirstCompletelyVisibleItemPositions(null);
ViewHolder firstViewHolder = rvPlantios.findViewHolderForLayoutPosition(viewsIds[0]);
View itemView = viewHolder.itemView;

Не забудьте проверить, если он пуст.

Для тех, кто ищет ответ в Котлине -

          fun getVisibleItem(recyclerView : RecyclerView) {
        recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                 if(newState == RecyclerView.SCROLL_STATE_IDLE) {
                     val index = (recyclerView.layoutManager.findFirstVisibleItemPosition
                     //use this index for any operation you want to perform on the item visible on screen. eg. log(arrayList[index])
                 }
            }
        })
    }

Вы можете изучить другие методы получения позиции в соответствии с вашим вариантом использования.

      int findFirstCompletelyVisibleItemPosition()
int findLastVisibleItemPosition()
int findLastCompletelyVisibleItemPosition()

Вы можете найти первого и последнего видимых дочерних элементов представления корзины и проверить, находится ли представление, которое вы ищете, в диапазоне:

var visibleChild: View = rv.getChildAt(0)
val firstChild: Int = rv.getChildAdapterPosition(visibleChild)
visibleChild = rv.getChildAt(rv.childCount - 1)
val lastChild: Int = rv.getChildAdapterPosition(visibleChild)
println("first visible child is: $firstChild")
println("last visible child is: $lastChild")

Я надеюсь, что приведенный ниже код поможет кому-то определить int aвыше методы. если позиция видимого элемента отличается до того, как на экране появится всплывающее сообщение о позиции элемента

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

            LinearLayoutManager manager= (LinearLayoutManager) myRecyclerview.getLayoutManager();
            assert manager != null;
            int visiblePosition = manager.findLastCompletelyVisibleItemPosition();


            if(visiblePosition > -1&&a!=visiblePosition) {
                Toast.makeText(context,String.valueOf(visiblePosition),Toast.LENGTH_SHORT).show();
                //do something
                a=visiblePosition;

            }
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //Some code while the list is scrolling


        }
    });

Я сделал так. При прокрутке, когда новый элемент достигает самой верхней позиции экрана, он возвращает позицию элемента, откуда вы можете получить элемент из массива.

      var currentItemPosition = RecyclerView.NO_POSITION
        binding?.recyclerMain?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                val firstVisibleItemPosition = (_binding?.recyclerMain?.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
                if (firstVisibleItemPosition != RecyclerView.NO_POSITION && firstVisibleItemPosition != currentItemPosition) {
                    val itemView = (_binding?.recyclerMain?.layoutManager as LinearLayoutManager).findViewByPosition(firstVisibleItemPosition)
                    val itemViewTop = itemView?.top ?: 0

                    recyclerView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
                        override fun onGlobalLayout() {
                            val recyclerViewTop = recyclerView.top
                            val itemTopInRecyclerView = itemViewTop - recyclerViewTop
                            if (itemTopInRecyclerView > 0) {
                                Log.d("RecyclerView", "Item at position $firstVisibleItemPosition reached the top")
                                listProduct(firstVisibleItemPosition).getProductName()
                            }
                            recyclerView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                            currentItemPosition = firstVisibleItemPosition
                        }
                    })
                }
            }
        })
Другие вопросы по тегам