Как создать круговой (бесконечный) 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
Ответы
Ответ 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.