如何创建循环(无限)的 RecyclerView?
How do I create a circular (endless) RecyclerView?
我正在尝试让我的 RecyclerView
循环回到列表的开头。
我在整个 Internet 上进行了搜索,并设法检测到我何时到达了列表的末尾,但是我不确定从哪里开始。
这是我目前用来检测列表结尾的方法(找到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);
}
}
}
当滚动到末尾时,我希望视图在显示列表顶部的数据时可见,或者当滚动到列表顶部时,我会从列表底部显示数据。
例如:
View1 View2 View3 View4 View5
View5 View1 View2 View3 View4
没有办法让它变成无限,但有办法让它看起来像无限。
在你的适配器中覆盖 getCount()
到 return 像 Integer.MAX_VALUE
:
这样大的东西
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
in getItem()
and 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);
我为这个问题找到的其他解决方案工作得很好,但我认为在 recycler 视图的 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);
}
除了上述解决方案。
对于双方无尽的回收者视图,您应该添加类似的内容:
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;
}
它的工作方式类似于无限向下滚动,但是是双向的。乐于助人!
两边无尽的recyclerView
在你的 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;
}
修改了@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()
中此解决方案的最小 return 值是 items.size + 3
。永远不会到达位置大于此的项目。
我运行正在研究 Glide 和其他 API 的 OOM 问题,并使用受此 post 启发的重复端盖创建了此实现,用于 iOS 构建。
可能看起来很吓人,但它实际上只是复制 RecyclerView class 并更新 RecyclerView 适配器中的两个方法。它所做的只是一旦它到达端盖,它就会快速无动画过渡到适配器的 ViewHolder 的两端,以允许连续循环过渡。
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。
我创建了一个 LoopingLayoutManager 来解决这个问题。
它无需修改适配器即可工作,从而具有更大的灵活性和可重用性。
它功能齐全,支持:
- 垂直和水平方向
- LTR 和 RTL
- 两个方向的 ReverseLayout,以及 LTR 和 RTL
- Public 查找项目和位置的函数
- Public 以编程方式滚动的函数
- Snap 助手支持
- 辅助功能(TalkBack 和语音访问)支持
它托管在 Maven Central 上,这意味着您只需将它作为依赖项添加到您的 build.gradle 文件中:
dependencies {
implementation 'com.github.beksomega:loopinglayout:0.3.1'
}
并将您的 LinearLayoutManager
更改为 LoopingLayoutManager
。
它有一套 132 个单元测试,让我相信它是稳定的,但如果您发现任何错误,请在 github!
上提出问题
希望对您有所帮助!
我正在尝试让我的 RecyclerView
循环回到列表的开头。
我在整个 Internet 上进行了搜索,并设法检测到我何时到达了列表的末尾,但是我不确定从哪里开始。
这是我目前用来检测列表结尾的方法(找到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);
}
}
}
当滚动到末尾时,我希望视图在显示列表顶部的数据时可见,或者当滚动到列表顶部时,我会从列表底部显示数据。
例如:
View1 View2 View3 View4 View5
View5 View1 View2 View3 View4
没有办法让它变成无限,但有办法让它看起来像无限。
在你的适配器中覆盖
这样大的东西getCount()
到 return 像Integer.MAX_VALUE
:@Override public int getCount() { return Integer.MAX_VALUE; }
in
getItem()
andgetView()
模除 (%) 位置除以实数:@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);
我为这个问题找到的其他解决方案工作得很好,但我认为在 recycler 视图的 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);
}
除了上述解决方案。 对于双方无尽的回收者视图,您应该添加类似的内容:
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;
}
它的工作方式类似于无限向下滚动,但是是双向的。乐于助人!
两边无尽的recyclerView
在你的 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;
}
修改了@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()
中此解决方案的最小 return 值是 items.size + 3
。永远不会到达位置大于此的项目。
我运行正在研究 Glide 和其他 API 的 OOM 问题,并使用受此 post 启发的重复端盖创建了此实现,用于 iOS 构建。
可能看起来很吓人,但它实际上只是复制 RecyclerView class 并更新 RecyclerView 适配器中的两个方法。它所做的只是一旦它到达端盖,它就会快速无动画过渡到适配器的 ViewHolder 的两端,以允许连续循环过渡。
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。
我创建了一个 LoopingLayoutManager 来解决这个问题。
它无需修改适配器即可工作,从而具有更大的灵活性和可重用性。
它功能齐全,支持:
- 垂直和水平方向
- LTR 和 RTL
- 两个方向的 ReverseLayout,以及 LTR 和 RTL
- Public 查找项目和位置的函数
- Public 以编程方式滚动的函数
- Snap 助手支持
- 辅助功能(TalkBack 和语音访问)支持
它托管在 Maven Central 上,这意味着您只需将它作为依赖项添加到您的 build.gradle 文件中:
dependencies {
implementation 'com.github.beksomega:loopinglayout:0.3.1'
}
并将您的 LinearLayoutManager
更改为 LoopingLayoutManager
。
它有一套 132 个单元测试,让我相信它是稳定的,但如果您发现任何错误,请在 github!
上提出问题希望对您有所帮助!