如何创建循环(无尽)RecyclerView?

时间:2015-07-06 18:59:10

标签: android android-recyclerview

我正在尝试将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);
            }
        }
 }

滚动到最后,我希望在从列表顶部显示数据或滚动到列表顶部时可以看到视图我将从列表底部显示数据。

例如:

  

View1 View2 View3 View4 View5

     

View5 View1 View2 View3 View4

7 个答案:

答案 0 :(得分:58)

没有办法让它变得无限,但有一种方法可以使它看起来像无限。

    适配器覆盖root中的
  1. 返回getCount()之类的内容:

    Integer.MAX_VALUE
  2. @Override public int getCount() { return Integer.MAX_VALUE; } getItem()模数除以(%)位置的实际商品编号:

    getView()
  3. 最后,将当前项目设置为中间的某个项目(否则,只有向下方向才会是无穷无尽的。)

    @Override
    public Fragment getItem(int position) {
        int positionInList = position % fragmentList.size();
        return fragmentList.get(positionInList);
    }
    

答案 1 :(得分:15)

我发现这个问题的其他解决方案运行得很好,但我认为在回收站视图的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);
}

答案 2 :(得分:7)

我创建了一个LoopingLayoutManager来解决此问题。

它无需修改适配器即可工作,从而具有更大的灵活性和可重用性。

它具有以下功能:

  • 垂直和水平方向
  • LTR和RTL
  • 两个方向的ReverseLayout以及LTR和RTL
  • 用于查找项目和位置的公共功能
  • 用于以编程方式滚动的公共功能
  • Snap Helper支持
  • 可访问性(对讲和语音访问)支持

它托管在Maven Central上,这意味着您只需将其作为依赖项添加到build.gradle文件中即可。

dependencies {
    implementation 'com.github.beksomega:loopinglayout:0.3.1'
}

并将您的LinearLayoutManager更改为LoopingLayoutManager

它具有132个单元测试套件,使我确信它的稳定性,但是如果发现任何错误,请在github上提出问题!

我希望这会有所帮助!

答案 3 :(得分: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;
}

它就像无限向下滚动一样工作,但是双向。很高兴提供帮助!

答案 4 :(得分:0)

两面都有无尽的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;
}

答案 5 :(得分:0)

改进的@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 :(得分:0)

我遇到了Glide和其他API的OOM问题,并使用受此帖子启发用于iOS构建的Duplicate End Caps创建了此实现。

可能看起来很吓人,但实际上只是复制RecyclerView类并更新RecyclerView Adapter中的两个方法。它所要做的就是,一旦碰到端盖,它就会在适配器的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。