如何创建循环(无限)的 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

没有办法让它变成无限,但有办法让它看起来像无限。

  1. 在你的适配器中覆盖 getCount() 到 return 像 Integer.MAX_VALUE:

    这样大的东西
    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }
    
  2. in getItem() and getView() 模除 (%) 位置除以实数:

    @Override
    public Fragment getItem(int position) {
        int positionInList = position % fragmentList.size();
        return fragmentList.get(positionInList);
    }
    
  3. 最后,将当前项目设置为中间的东西(否则,只有向下的方向才会无穷无尽)。

    // 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!

上提出问题

希望对您有所帮助!