Подтвердить что ты не робот

Как создать круговой (бесконечный) RecyclerView?

Я пытаюсь сделать мой цикл RecyclerView обратно в начало моего списка.

Я искал по всему Интернету и сумел обнаружить, когда я дошел до конца своего списка, однако я не уверен, где исходить отсюда.

Это то, что я использую в настоящее время для обнаружения конца списка (нашёл здесь):

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

        visibleItemCount = mLayoutManager.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if ( (visibleItemCount+pastVisiblesItems) >= totalItemCount) {
                loading = false;
                Log.v("...", ""+visibleItemCount);
            }
        }
 }

Когда прокручивается до конца, я хотел бы, чтобы представления отображались во время отображения данных из верхней части списка или при прокрутке вверху списка, я бы отображал данные из нижней части списка.

Например:

View1 View2 View3 View4 View5

View5 View1 View2 View3 View4

4b9b3361

Ответ 1

Нет никакого способа сделать его бесконечным, но есть способ сделать его похожим на бесконечный.

  • в вашем адаптере переопределить getCount(), чтобы вернуть что-то большое, как Integer.MAX_VALUE:

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }
    
  • в getItem() и getView() по модулю делить (%) позицию по номеру реального элемента:

    @Override
    public Fragment getItem(int position) {
        int positionInList = position % fragmentList.size();
        return fragmentList.get(positionInList);
    }
    
  • в конце, установите текущий элемент на что-то посередине (иначе он будет бесконечным только в нисходящем направлении).

    // scroll to middle item
    recyclerView.getLayoutManager().scrollToPosition(Integer.MAX_VALUE / 2);
    

Ответ 2

Другие решения, которые я нашел для этой проблемы, работают достаточно хорошо, но я думаю, что могут быть некоторые проблемы с памятью, возвращающие Integer.MAX_VALUE в методе getCount() представления переработчика.

Чтобы это исправить, переопределите метод getItemCount(), как показано ниже:

@Override
public int getItemCount() {
    return itemList == null ? 0 : itemList.size() * 2;
}

Теперь, где бы вы ни использовали позицию, чтобы получить элемент из списка, используйте ниже

position % itemList.size()

Теперь добавьте scrollListener к вашему представлению переработчика

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 
        super.onScrolled(recyclerView, dx, dy);
        int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
        if (firstItemVisible != 0 && firstItemVisible % itemList.size() == 0) {
            recyclerView.getLayoutManager().scrollToPosition(0);
        }
    }
});

Наконец, чтобы начать автоматическую прокрутку, вызовите метод ниже

public void autoScroll() {
    final Handler handler = new Handler();
    final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            recyclerView.scrollBy(2, 0);
            handler.postDelayed(this, 0);
        }
    };
    handler.postDelayed(runnable, 0);
}

Ответ 3

В дополнение к решению выше. Для бесконечного просмотра переработчика с обеих сторон вы должны добавить что-то вроде этого:

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            val firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition()
            if (firstItemVisible != 1 && firstItemVisible % songs.size == 1) {
                linearLayoutManager.scrollToPosition(1)
            }
            val firstCompletelyItemVisible = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
            if (firstCompletelyItemVisible == 0) {
                linearLayoutManager.scrollToPositionWithOffset(songs.size, 0)
            }
        }
    })

И обновите ваш метод getItemCount():

@Override
public int getItemCount() {
        return itemList == null ? 0 : itemList.size() * 2 + 1;
}

Это похоже на неограниченную прокрутку, но в обоих направлениях. Рад помочь!

Ответ 4

Бесконечный переработчикПросмотреть в обе стороны

Добавьте onScrollListener в свой обзор переработчиков

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

            int firstItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
            if (firstItemVisible != 1 && firstItemVisible % itemList.size() == 1) {
                ((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPosition(1);
            }
            int firstCompletelyItemVisible = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
            if (firstCompletelyItemVisible == 0)
            {}

            if (firstItemVisible != RecyclerView.NO_POSITION
                    && firstItemVisible== recyclerView.getAdapter().getItemCount()%itemList.size() - 1)
            {
                ((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(itemList.size() + 1, 0);
            }
        }
    });

В вашем адаптере переопределите метод getItemCount

@Override
public int getItemCount()
{
    return itemList == null ? 0 : itemList.size() * 2 + 1;
}

Ответ 5

Исправлено решение @afanit, чтобы предотвратить мгновенную остановку бесконечной прокрутки при прокрутке в обратном направлении (из-за ожидания, когда 0-й элемент станет полностью видимым, что позволяет прокручиваемому контенту заканчиваться до scrollToPosition()):

val firstItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstItemPosition != 1 && firstItemPosition % items.size == 1) {
    layoutManager.scrollToPosition(1)
} else if (firstItemPosition == 0) {
    layoutManager.scrollToPositionWithOffset(items.size, -recyclerView.computeHorizontalScrollOffset())
}

Обратите внимание на использование computeHorizontalScrollOffset() потому что мой менеджер компоновки расположен горизонтально.

Кроме того, я обнаружил, что минимальное возвращаемое значение из getItemCount() для этого решения - items.size + 3. Предметы с позицией больше этой никогда не достигаются.

Ответ 6

Я столкнулся с проблемами OOM с Glide и другими API и создал эту реализацию, используя Duplicate End Caps, вдохновленные этим постом для сборки iOS.

Может показаться пугающим, но это буквально просто копирование класса RecyclerView и обновление двух методов в вашем адаптере RecyclerView. Все, что он делает, так это то, что, как только он попадает в заглавные буквы, он выполняет быстрый переход без анимации к обоим концам адаптера ViewHolders, чтобы обеспечить непрерывные циклические переходы.

http://iosdevelopertips.com/user-interface/creating-circular-and-infinite-uiscrollviews.html

class CyclingRecyclerView(
    context: Context,
    attrs: AttributeSet?
) : RecyclerView(context, attrs) {
    // --------------------- Instance Variables ------------------------
    private val onScrollListener = object : RecyclerView.OnScrollListener() {

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            // The total number of items in our RecyclerView
            val itemCount = adapter?.itemCount ?: 0

            // Only continue if there are more than 1 item, otherwise, instantly return
            if (itemCount <= 1) return

            // Once the scroll state is idle, check what position we are in and scroll instantly without animation
            if (newState == SCROLL_STATE_IDLE) {
                // Get the current position
                val pos = (layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()

                // If our current position is 0,
                if (pos == 0) {
                    Log.d("AutoScrollingRV", "Current position is 0, moving to ${itemCount - 1} when item count is $itemCount")
                    scrollToPosition(itemCount - 2)
                } else if (pos == itemCount - 1) {
                    Log.d("AutoScrollingRV", "Current position is ${itemCount - 1}, moving to 1 when item count is $itemCount")
                    scrollToPosition(1)
                } else {
                    Log.d("AutoScrollingRV", "Curren position is $pos")
                }
            }
        }
    }

    init {
        addOnScrollListener(onScrollListener)
    }
}

Для адаптера просто убедитесь, что обновлены 2 метода, в моем случае viewModels - это просто моя структура данных, которая содержит данные, которые я отправляю в мои ViewHolders

override fun getItemCount(): Int = if (viewModels.size > 1) viewModels.size + 2 else viewModels.size

и в ViewHolder вы просто извлекаете скорректированные данные индекса

    override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
        val adjustedPos: Int =
            if (viewModels.size > 1) {
                when (position) {
                    0 -> viewModels.lastIndex
                    viewModels.size + 1 -> 0
                    else -> position - 1
                }
            } else {
                position
            }
        holder.bind(viewModels[adjustedPos])
    }

Предыдущая реализация меня обидела, ха-ха. Казалось, это было просто хакерским - просто добавить сумасшедшее количество элементов, большая проблема, когда вы сталкиваетесь с несколькими картами с вложенным RecyclerView с Integer.MAX_VALUE. Этот подход исправил все проблемы OOM, поскольку он только обязательно создает 2 и ViewHolders.