如何创建一个循环(无尽的)RecyclerView?
Posted
技术标签:
【中文标题】如何创建一个循环(无尽的)RecyclerView?【英文标题】:How do I create a circular (endless) RecyclerView? 【发布时间】:2015-09-24 01:18:58 【问题描述】:我正在尝试让我的 RecyclerView
循环回到我列表的开头。
我在整个互联网上进行了搜索,并设法检测到我何时到达了列表的末尾,但是我不确定从哪里开始。
这是我目前用来检测列表末尾的(找到here):
@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);
当滚动到末尾时,我希望视图在从列表顶部显示数据时可见,或者当滚动到列表顶部时,我会从列表底部显示数据。
例如:
查看1 查看2 查看3 查看4 查看5
查看5 查看1 查看2 查看3 查看4
【问题讨论】:
@DamianKozlak RecyclerView 与 ListView 不同 @ChadBingham 当然不是,但是可以使用与ListView
中相同的解决方案来解决此问题。 ListView
和 RecyclerView
适配器都有 getCount()
和 getItem()
方法。
您找到解决方案了吗?我正在解决同样的问题,目前我正在考虑编写一个从 LinearLayoutManager 扩展的自定义 LayoutManager。你有没有实现过你的目标?
是的,Sebastian 下面的回答对我有用。这是我找到的最简单的解决方案。
【参考方案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);
【讨论】:
@anhtuannd 使用 MAX_INT / 2 或其他任何内容作为起始位置。 很好的解决方案!我添加了代码以便更好地理解。 :) 好主意!一项改进是在 scrollTo() 调用之后显示相同的第一项。可能是:int number = Integer.MAX_VALUE / items.size() / 2; scrollToPosition(number * items.size()); 我不明白这是怎么工作的,首先我们在谈论 Recyclerview 适配器,getItem / getView 是从哪里来的。 @geniushkg 这实际上更简单。您不需要覆盖任何方法,只需在onBindViewHolder()
中使用 it = position % itemList.size()
的另一个位置(例如 realPosition)。【参考方案2】:
我为这个问题找到的其他解决方案运行良好,但我认为在回收器视图的 getCount() 方法中返回 Integer.MAX_VALUE 可能存在一些内存问题。
要解决此问题,请重写 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);
【讨论】:
我的这个方法只重复了一次,例如我输入了虚拟数据1,2,3,4,它只显示1,2,3,4,1,2,3,4,而不是无限循环,我做错了什么? @BeeingJk 在 getItemCount 函数中,我们返回列表中的两次元素,这就是为什么它只重复两次。尝试返回列表中元素数量的 4-5 倍,然后它应该可以正常工作。 感谢@NikhilGupta,我已经弄清楚了,因为我的数据长度太短以至于firstItemVisible % itemList.size() == 0
永远不会发生,是的,返回 3 次使它工作,但我不能保证它会一直在工作,所以我在启用自动滚动之前将总数据长度与 RecyclerView 宽度进行比较,到目前为止找不到更简单的方法
我无法从头循环到结尾,但它正在从结尾循环到开头。有什么方法可以让我朝任何一个方向走。
我需要从哪里调用 autoScroll() 方法请帮忙。【参考方案3】:
我创建了一个LoopingLayoutManager 来解决这个问题。
它无需修改适配器即可工作,从而具有更大的灵活性和可重用性。
它功能齐全,支持:
垂直和水平方向 LTR 和 RTL 两个方向以及 LTR 和 RTL 的 ReverseLayout 用于查找项目和位置的公共函数 以编程方式滚动的公共函数 Snap Helper 支持 辅助功能(TalkBack 和语音访问)支持它托管在 maven Central 上,这意味着您只需将其作为依赖项添加到您的 build.gradle 文件中:
dependencies
implementation 'com.github.beksomega:loopinglayout:0.3.1'
并将您的 LinearLayoutManager
更改为 LoopingLayoutManager
。
它有一套 132 个单元测试,这让我确信它是稳定的,但如果你发现任何错误,请在 github 上提出问题!
我希望这会有所帮助!
【讨论】:
这很好,但我注意到了一个问题,即项目在滚动时会跳转位置。 感谢您试用我的项目@DaveThomas!我也非常感谢您报告有关它的错误。我从来没有见过这个问题,所以我不能立即给你建议。但是,如果您填写 issue,我也许可以找到它。 对我来说,推荐的图书馆就像一个魅力。我很感激它也支持LinearSnapHelper
!
现在我看到LoopingLayoutManager
的问题在于它多次布置/渲染相同的View
实例。乍一看,它看起来很棒,但如果需要更新项目,这种方法不适用于场景。 onClick
。在这种情况下,行为是未指定的(有点混乱),因为未单击的项目也会更新其 UI。发生这种情况是因为这些项目共享相同的 View
实例。从编码的角度来看,上述Integer.MAX_VALUE % 2
方法可能不是最干净的方法,但适用于这种情况。【参考方案4】:
修改了@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
。永远不会到达位置大于此的项目。
【讨论】:
【参考方案5】:除了上面的解决方案。 对于双方的无尽回收者视图,您应该添加类似的内容:
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;
它的工作方式类似于无限向下滚动,但是是双向的。很高兴为您提供帮助!
【讨论】:
它没有得到最后一个项目之后的第一个项目,它只是滚动回到顶部【参考方案6】:我在使用 Glide 和其他 API 时遇到了 OOM 问题,并使用受这篇文章启发的 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])
以前的实现让我很受伤哈哈,似乎只是添加大量的项目,这是一个很大的问题,当您遇到带有 Integer.MAX_VALUE 嵌套 RecyclerView 的多张卡片时。这种方法解决了 OOM 的所有问题,因为它只需要创建 2 和 ViewHolders。
【讨论】:
【参考方案7】:两边都有无尽的recyclerView
在你的回收站视图中添加 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;
【讨论】:
以上是关于如何创建一个循环(无尽的)RecyclerView?的主要内容,如果未能解决你的问题,请参考以下文章
无尽的recyclerview,多种视图类型,OnscrollListener 后崩溃
java 无尽的RecyclerView OnScrollListener
如果循环一个数据框并在该循环内创建新列,它会是无穷无尽的吗?
像素风Roguelike策略卡牌《循环英雄》:无尽循环中的成长与取舍