【问题标题】:RecyclerView - get all existing views/viewholdersRecyclerView - 获取所有现有的视图/视图持有者
【发布时间】:2019-07-07 18:00:43
【问题描述】:

我想在 RecyclerView 显示数据时更新它,就我而言,我显示带有或不带标签的图像。

默认情况下,我在创建视图持有者时设置标签的可见性,这很好,但我希望用户在显示 RecyclerView 时通过菜单更改标签可见性,所以我想手动更新可见性对于RecyclerView 中的所有现有视图。

我能否以某种方式获得所有现有的Views?我需要所有,不仅是可见的,我不希望以后回收的View 不更新...

【问题讨论】:

  • 你有什么参数可以知道标签在onBindViewHolder中是否可见吗?您应该检查此参数。更新菜单中的参数并设置adapter.notifyDataSetChanged
  • 我正在阅读首选项,所以我想这样做一次而不是总是......我也可以在适配器中使用内部布尔值来做到这一点,实际上并没有考虑到这一点...
  • 这个参数对所有的item都一样吗?或者可以根据项目不同?
  • 对所有人都一样...这就是为什么我更喜欢迭代所有现有视图...
  • 您可以将此值传递给adpater构造函数,并在adpater中创建一个方法来更改此值

标签: android android-recyclerview


【解决方案1】:

首先您需要获取所有显示的视图索引,然后您需要遍历每个视图,并使用每个视图的 viewHolder:

final int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
    ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(i);
    ...
    }

编辑:似乎它并不总是返回您可能想要处理的所有 ViewHolders。这似乎是一个更稳定的解决方案:

for (int childCount = recyclerView.getChildCount(), i = 0; i < childCount; ++i) {
   final ViewHolder holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
   ...
   }

注意:在某些情况下,您可能希望将缓存的视图数量设置为足够大,以便始终回收相同的视图,而不是新的视图。为此,您可以使用类似的东西:

fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
    for (i in 0..maxViewTypeId)
        recycledViewPool.setMaxRecycledViews(i, maxPoolSize)
}

示例用法:

recyclerView.setMaxViewPoolSize(MAX_TYPE_ITEM, Int.MAX_VALUE)

【讨论】:

  • 这是“获取所有现有视图/视图持有者”问题的正确答案,正是我所需要的。非常感谢!
  • @JasonTyler 为了使用 RecyclerView,你总是创建一个 LayoutManager 实例,所以你之前就有了。这是创建它的一种方法,像垂直列表一样工作(但同样,您需要选择使用哪个): LayoutManager layoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
  • 这也不正确,有些绑定的视图持有者还不可见:在启动加载后,通常有 2 或 3 个项目绑定在最后一个可见位置。
  • @androiddeveloper,我刚刚尝试观看我放置的日志,它绑定了更多不可见的项目,实际上我找不到检索它们的方法,我最终缓存在一组或列出那些ViewHoldersonBindViewHolder() 中并在onViewRecycled() 中取消缓存它们
  • 我可以确认@Davidea 的发现是正确的。缓存的视图持有者比 (lastVisibleItemPosition - firstVisibleItemPosition + 1)
【解决方案2】:
  • 使用notifyDataSetChanged() 更新整个适配器列表大小并不方便。
  • 获取所有当前 RecyclerView 子项(即所有可见项)是不够的:存在不可见的绑定视图持有者:在启动时加载后通常有 2 或 3 个项绑定在最后一个可见位置(取决于您的 LayoutManager)。

我刚刚尝试查看我放置的日志,RV 绑定了更多不可见的项目,实际上我没有找到找回它们的方法。我最终在SetList缓存 onBindViewHolder() 中的那些ViewHolders,并在onViewRecycled()取消缓存它们:

private Set<AbstractViewHolder> mBoundViewHolders = new HashSet<>();
private int mVisibility = View.VISIBILE;

// So you can have access from the Activity/Fragment
public Set<AbstractViewHolder> getAllBoundViewHolders() {
    return Collections.unmodifiableSet(mBoundViewHolders);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
    // Binding code
    ...
    holder.view.setVisibility(mVisibility);

    // Caching VH
    mBoundViewHolders.add((AbstractViewHolder) holder);
}

@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
    // Uncaching VH
    mBoundViewHolders.remove(holder);
}

// Your method becomes
public void updateVisibility(int visibility) {
    mVisibility = visibility;
    for (AbstractViewHolder holder : mBoundViewHolders) {
        holder.updateVisibility(visibility);
    }
}

实现您的接口或抽象 ViewHolder,以便您可以调用您的方法。 通过这种方式,您可以访问您的 ViewHolder 内容。


这就是我在FlexibleAdapter 中所做的,以取消选择项目而不调用notifyItemRangeChanged()。如果用户有动画,所有动画都会被执行,而且当我调用我的方法toggleActivation()时背景也会发生变化。

如果您知道检索所有绑定 VH 的更好方法,请告诉我。

【讨论】:

  • 如果我只是将所有 ViewHolder 存储在 onCreateViewHolder 中而不是添加和删除到集合中会发生什么?
  • @androiddeveloper 在我的库中它无法工作,因为尚未创建 VH。但是您可以尝试看看您的适配器会发生什么。
  • @Davidea 我之前没有给你这个答案!很好的解决方法!
  • 使用WeakReference存储VH更好吗?
  • 我可以推荐使用这个数据结构来存储缓存的 VH - Set&lt;AbstractViewHolder&gt; mBoundViewHolders = Collections.newSetFromMap(new java.util.WeakHashMap&lt;AbstractViewHolder, Boolean&gt;());
【解决方案3】:

在适配器类中:

Boolean isVisible = false;

public CustomAdapter(boolean isVisible) {
    this.isVisible= isVisible;
}
    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
     ...

         if (isVisible){
           //setVisibility(View.VISIBLE)
         }else{
         //setVisibility(View.INVISIBLE
         }
    }

public void updateVisibility(boolean newValue){
 isVisible= newValue;
}

Activity中要更新实例化适配器的值:

adapter.updateVisibility(false);
adapter.notifydataSetChanged();

【讨论】:

  • updateVisibility 中,您必须添加notifyDataSetChanged(),但这是可行的,这就是我目前的解决方案......不过,对我来说,这似乎并不完美,用户很少更改可见性,而他几乎总是滚动,所以一直设置可见性对我来说并不是最有效的解决方案......
  • @prom85,在 View 类中,有一些检查可见性是否已更改,因此如果您使用相同的值调用 setVisibility,这不是问题。
  • 我的测试表明 prom85 是对的:我确实必须调用 notifyDataSetChanged() 来强制 RecyclerView 重新绘制已显示但需要更改的项目中的更改.
  • 非常感谢您
  • 此解决方案是此页面中的最佳解决方案,因为它是唯一更新所有 recyclerview 项目可见性的解决方案 - 包括那些尚未滚动的项目!
【解决方案4】:

所有现有视图都是可见的,加上RecyclerView 缓存的一些视图。您可以像这样访问所有可见的Views:

View child;
for (int i = 0; i < mRecycler.getChildCount(); i++) {
    child = mRecycler.getChildAt(i);
    //In case you need to access ViewHolder:
    mRecycler.getChildViewHolder(child);
}

即使您访问缓存的Views,它们还没有绑定到数据,因此您不会知道标签是否应该可见。只需在onBindViewHolder 中设置剩余Views 的标签可见性。

Views 的其余部分根本不存在,因此您无法更改它们的任何内容,即使它们的标签可见性状态都相同。

【讨论】:

  • 这不起作用,如果我这样做,似乎并非所有缓存的视图都会返回......
  • 我在哪里写到这可以让您访问缓存视图?无论如何访问它们是没有意义的。只需像上面说的那样修改 visible 视图,其余的应该在RecyclerView 需要时在onBindViewHolder 中修改。
  • 我说:“所有现有都是那些可见加上一些缓存的视图”。然后:“你可以像这样访问所有 visible 视图:” 没有关于访问 cached 视图的方法。如果您希望在视图滚动到屏幕外并在“onBindViewHolder”中反弹之前应用可见性更改,则确实需要修改可见视图。问题不再是问题。问题在于你对问题的态度。
【解决方案5】:

上述所有解决方案都只给出了可见的持有者,但实际上还有 2-3 个其他持有者被绑定但不可见。为了得到这些, 我能想到的获得所有持有者的最简单和准确的方法是:

int lastBoundHolderPosition = -1;
int lastDetachedHolderPosition = -1;

public void onBindViewHolder(final MyViewHolder holder, int position) {
    lastBoundHolderPosition = position;
    //Your implementation
}
public void onViewDetachedFromWindow(MyViewHolder holder){
    lastDetachedHolderPosition = holder.holder.getAdapterPosition();
}

当您希望所有适配器可见(加上绑定但不可见)时:

if(lastDetachedHolderPosition == -1){
    for(pos = 0; pos <= lastDetachedHolderPosition; pos++){
        ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
    }
}else{
    if(lastBoundHolderPosition > lastDetachedHolderPosition){ //Scroll down
        //get all the bound holders
        for(int pos = lastDetachedHolderPosition + 1; pos <= lastBoundHolderPosition; pos++){
            ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
        }
    }else{ //Scroll up
        //get all the bound holders
        for(int pos = lastBoundHolderPosition ; pos <= lastDetachedHolderPosition-1; pos++){
            ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
        }

    }
}

【讨论】:

  • 如何在不滚动时获取视图?例如,点击按钮后?
  • hmmm... 另一种类似的方法是拥有一组 ViewHolders,这也将避免调用 "findViewHolderForAdapterPosition" 。可以更短,并且可能更有效。没有?
  • 我们只想要当前绑定的VH,唯一的方法就是监控它们何时被回收(=unbound),这就是我提出的解决方案! onViewDetachedFromWindow() 在 VH 不可见但仍被绑定时调用,因此不够精确。
  • 第一个条件没有意义。你检查是否 lastDetachedHolderPosition==-1 ,如果是,从 0 到它,意思是从 0 到 -1 。这意味着循环永远不会做任何事情。
猜你喜欢
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 2011-05-07
  • 1970-01-01
  • 2014-12-16
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
相关资源
最近更新 更多