【问题标题】:How do I create a circular (endless) RecyclerView?如何创建一个循环(无尽的)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 中相同的解决方案来解决此问题。 ListViewRecyclerView 适配器都有 getCount()getItem() 方法。
  • 您找到解决方案了吗?我正在解决同样的问题,目前我正在考虑编写一个从 LinearLayoutManager 扩展的自定义 LayoutManager。你有没有实现过你的目标?
  • 是的,Sebastian 下面的回答对我有用。这是我找到的最简单的解决方案。

标签: android android-recyclerview


【解决方案1】:

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

  1. 在您的适配器中覆盖 getCount() 以返回像 Integer.MAX_VALUE 这样的大内容:

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }
    
  2. getItem()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);
    

【讨论】:

  • @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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-05
        • 2015-01-24
        • 2022-07-12
        • 1970-01-01
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多