【问题标题】:Get visible items in RecyclerView获取 RecyclerView 中的可见项目
【发布时间】:2014-09-22 10:58:49
【问题描述】:

我需要知道我的 RecyclerView 中当前显示了哪些元素。 ListViews 上没有与OnScrollListener.onScroll(...) 方法等效的方法。我尝试与 View.getGlobalVisibleRect(...) 合作,但这种 hack 太丑陋了,而且并不总是有效。

有人有什么想法吗?

【问题讨论】:

    标签: android android-recyclerview


    【解决方案1】:

    第一个/最后一个可见的孩子取决于LayoutManager。 如果你使用LinearLayoutManagerGridLayoutManager,你可以使用

    int findFirstVisibleItemPosition();
    int findFirstCompletelyVisibleItemPosition();
    int findLastVisibleItemPosition();
    int findLastCompletelyVisibleItemPosition();
    

    例如:

    GridLayoutManager layoutManager = ((GridLayoutManager)mRecyclerView.getLayoutManager());
    int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
    

    对于LinearLayoutManager,第一个/最后一个取决于适配器顺序。不要从RecyclerView 查询孩子; LayoutManager 可能更喜欢布局比可见的缓存更多的项目。

    【讨论】:

    • 是否有可能在不传递对 LayoutManager 的引用的情况下从 RecyclerView.Adapter 访问这些方法?
    • 就像@Yorgi - 在适配器内部似乎很有用。我想知道您是否可以开发一些模式来使用 onBindViewHolder 在用户滚动时跟踪哪些实际上在屏幕上。
    • getItemCount 可以在同一个调用中返回 1 和 findFirstVisibleItemPosition -1。
    • 您对 StaggeredLayoutManager 有什么建议?
    • 这些方法太不可靠了,完全没用。特别是在 notifyDataSetChanged/itemRemoved/RangeChanged 之后
    【解决方案2】:

    对于那些在 RecyclerView 适配器中实现逻辑的人,您仍然可以使用 @ernesto 方法与 on scrollListener 结合来获取 what you want,因为咨询了 RecyclerView。 在适配器内部,您将拥有如下内容:

    @Override
        public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            if(manager instanceof LinearLayoutManager && getItemCount() > 0) {
                LinearLayoutManager llm = (LinearLayoutManager) manager;
                recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                        super.onScrollStateChanged(recyclerView, newState);
                    }
    
                    @Override
                    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                        super.onScrolled(recyclerView, dx, dy);
                            int visiblePosition = llm.findFirstCompletelyVisibleItemPosition();
                            if(visiblePosition > -1) {
                                View v = llm.findViewByPosition(visiblePosition);
                                //do something
                                v.setBackgroundColor(Color.parseColor("#777777"));
                            }
                    }
                });
            }
        }
    

    【讨论】:

    • llm 变量不需要是最终的,以便匿名类“认识”他吗?
    • 我不是真正的专家,但我认为问题不在于匿名类“知道”llm,而是为了确保何时调用回调,llm 的值与实例化匿名类以避免不一致时相同,如果我错了,请纠正我llm 的值从未改变过,当我发布我的答案时,我没有收到任何警告或编译器错误。如果我错了或遗漏了什么,请告诉我
    【解决方案3】:

    最后,我从适配器中的 onBindViewHolder 事件中找到了知道当前项是否可见的解决方案。

    关键是LayoutManager的方法isViewPartiallyVisible

    在您的适配器中,您可以从 RecyclerView 中获取 LayoutManager,它是您从 onAttachedToRecyclerView 事件中获取的参数。

    【讨论】:

    • 但是在布局管理器不为空的情况下,你会在哪里调用这个方法呢?
    • 需要等待适配器的onAttachedToRecyclerView事件。在那种情况下,您会收到 RecyclerView 作为参数;然后您可以从 RecyclerVie 中获取 LayoutManager,并将其保存在全局变量中。如果 LayoutManager 为 null,可能是因为它没有分配给 Activity/Fragment/Layout 中的 RecyclerView;并尝试在分配之后构建适配器。
    • 根据源代码,isViewPartiallyVisible 严重损坏。如果fullyVisible 为true,如果视图完全可见,它将返回true。但是,如果为 false,它只是否定结果,如果视图部分可见或不可见,则返回 true。即使是文档也没有意义。
    • @ErnestoVega 但 onBindViewholder 会为同一个视图多次调用,这意味着即使视图被查看一次,isViewPartiallyVisible 也会多次返回 true
    【解决方案4】:

    您可以使用recyclerView.getChildAt() 来获取每个可见的孩子,并在适配器代码中的这些视图上设置一些标签convertview.setTag(index) 将帮助您将其与适配器数据相关联。

    【讨论】:

    • getChildAt()的问题是元素的顺序可以在5个元素中:0、5、4、3、2、1(子数大多>=可见项目)。所以我尝试通过 getGlobalVisibleRect 获取元素的顺序。
    • 您的实际要求是什么,按实际顺序获得可见的孩子(屏幕绑定的孩子)?
    • 现在我想知道哪个元素在中心,以后我可能也对边界感兴趣。
    • 您只能从recyclerView.getChildAt() 获得可见项目,这就是RecyclerView 的一般工作方式。 RecyclerView 将尝试仅保留少数当前可见的子视图(即;在屏幕范围内,而不是 Visibility as GONE、INVISIBLE),并在用户滚动时尝试回收这些视图。
    • 查看 visibleChild = recyclerView.getChildAt(0); int positionOfChild = recyclerView.getChildAdapterPosition(visibleChild);
    【解决方案5】:

    附录

    建议的函数 findLast...Position() 确实在工具栏展开而工具栏折叠的场景中正常工作。

    似乎回收器视图具有固定的高度,并且在工具栏展开时,回收器向下移动,部分移出屏幕。因此,建议的函数的结果太高了。示例:最后一个可见项目被告知是 #9,但实际上项目 #7 是屏幕上的最后一个。

    这种行为也是我的视图经常无法滚动到正确位置的原因,即 scrollToPosition() 无法正常工作(我最终以编程方式折叠了工具栏)。

    【讨论】:

    • 确实如此,但这就是折叠工具栏的工作方式。一年前我停止开发 Android 应用程序,但我记得这个事实。
    • 我面临同样的问题。除了折叠工具栏之外,您是否知道如何绕过它?
    【解决方案6】:

    以下Linear / Grid LayoutManager方法可用于检查哪些项目是visible

    int findFirstVisibleItemPosition();
    int findLastVisibleItemPosition();
    int findFirstCompletelyVisibleItemPosition();
    int findLastCompletelyVisibleItemPosition();
    

    如果您想跟踪is item visible on screen 的某个阈值,那么您可以参考以下博客。

    https://proandroiddev.com/detecting-list-items-perceived-by-user-8f164dfb1d05

    【讨论】:

      【解决方案7】:

      对于StaggeredGridLayoutManager,请执行以下操作:

      RecyclerView rv = findViewById(...);
      StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(...);
      rv.setLayoutManager(lm);
      

      并获得可见的项目视图:

      int[] viewsIds = lm.findFirstCompletelyVisibleItemPositions(null);
      ViewHolder firstViewHolder = rvPlantios.findViewHolderForLayoutPosition(viewsIds[0]);
      View itemView = viewHolder.itemView;
      

      记得检查是否为空。

      【讨论】:

      • “检查是否为空”是什么意思?如何检查?
      • viewsIds 是一个数组,可能为空。
      【解决方案8】:

      上面的每个答案都是正确的,我还想从我的工作代码中添加一个快照。

      recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
              @Override
              public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                  super.onScrollStateChanged(recyclerView, newState);
                 //some code when initially scrollState changes
              }
      
              @Override
              public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                  super.onScrolled(recyclerView, dx, dy);
                  //Some code while the list is scrolling
                  LinearLayoutManager lManager = (LinearLayoutManager) recycler.getLayoutManager();
                  int firstElementPosition = lManager.findFirstVisibleItemPosition();
                  
              }
          });
      

      【讨论】:

        【解决方案9】:

        您可以找到回收视图的第一个和最后一个可见子项,并检查您要查找的视图是否在范围内:

        var visibleChild: View = rv.getChildAt(0)
        val firstChild: Int = rv.getChildAdapterPosition(visibleChild)
        visibleChild = rv.getChildAt(rv.childCount - 1)
        val lastChild: Int = rv.getChildAdapterPosition(visibleChild)
        println("first visible child is: $firstChild")
        println("last visible child is: $lastChild")
        

        【讨论】:

        • 这不是 Java
        • 但是因为你在这里添加的一般内容确实帮助了我,所以点赞
        【解决方案10】:

        对于那些在 Kotlin 中寻找答案的人 -

            fun getVisibleItem(recyclerView : RecyclerView) {
                recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
                    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                         if(newState == RecyclerView.SCROLL_STATE_IDLE) {
                             val index = (recyclerView.layoutManager.findFirstVisibleItemPosition
                             //use this index for any operation you want to perform on the item visible on screen. eg. log(arrayList[index])
                         }
                    }
                })
            }
        

        您可以根据您的用例探索其他获取位置的方法。

        int findFirstCompletelyVisibleItemPosition()
        int findLastVisibleItemPosition()
        int findLastCompletelyVisibleItemPosition()
        

        【讨论】:

          【解决方案11】:

          我希望下面的代码可以帮助某人定义int a 以上方法。如果在项目位置吐司消息之前可见项目位置不同,则会在屏幕上显示

          myRecyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
                  @Override
                  public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                      super.onScrollStateChanged(recyclerView, newState);
          
                      LinearLayoutManager manager= (LinearLayoutManager) myRecyclerview.getLayoutManager();
                      assert manager != null;
                      int visiblePosition = manager.findLastCompletelyVisibleItemPosition();
          
          
                      if(visiblePosition > -1&&a!=visiblePosition) {
                          Toast.makeText(context,String.valueOf(visiblePosition),Toast.LENGTH_SHORT).show();
                          //do something
                          a=visiblePosition;
          
                      }
                  }
          
                  @Override
                  public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                      super.onScrolled(recyclerView, dx, dy);
                      //Some code while the list is scrolling
          
          
                  }
              });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-08-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多