【问题标题】:onBindViewHolder() is never called on view at position even though RecyclerView.findViewHolderForAdapterPosition() returns null at that position即使 RecyclerView.findViewHolderForAdapterPosition() 在该位置返回 null,也永远不会在该位置调用 onBindViewHolder()
【发布时间】:2016-10-19 13:05:09
【问题描述】:

我有一个包含 13 个项目的列表(尽管可能会添加或删除项目),位置为 0-12。当第一次显示包含 RecyclerView 的片段时,只有位置 0 到 7 对用户可见(位置 7 只有一半可见)。在我的适配器中,我 Log 每次绑定/绑定视图持有者(如果语法适用于此,则为 idk)并记录其位置。

适配器

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    Log.d(TAG, "onBindViewHolder() position: " + position);
    ...
}

从我的Log 我看到位置 0-7 已绑定:

我有一个selectAll() 方法,可以通过适配器位置获取每个ViewHolder。如果返回的holder 不是null,我使用返回的holder 来更新视图以显示它已被选中。如果返回的持有者是 null,我调用 selectOnBind() 一个方法,该方法在该位置更新标记视图以显示它在绑定时而不是实时选择,因为它当前未显示:

public void selectAll() {
    for (int i = 0; i < numberOfItemsInList; i++) {
        MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder)
                mRecyclerView.findViewHolderForAdapterPosition(i);

        Log.d(TAG, "holder at position " + i + " is " + holder);

        if (holder != null) {
            select(holder);
        } else {
            selectOnBind(i);
        }
    }
}

在这个方法中我Logholder连同它的位置:

所以到目前为止一切似乎都很正常。我们有位置 0-7 显示,根据Log 这些是绑定的位置。当我在不更改可见视图(滚动)的情况下点击selectAll() 时,我看到位置 0-7 已定义,8-12 为null。到目前为止一切顺利。

这就是有趣的地方。如果在调用selectAll() 之后我进一步向下滚动列表位置 8 和 9 不显示它们已被选中。

检查Log 时,我发现这是因为它们从未被绑定,即使它们被报告为null

更令人困惑的是,这种情况并非每次都发生。如果我首先启动该应用程序并对其进行测试,它可能会起作用。但它似乎在之后毫无失败地发生。我猜这与被回收的视图有关,但即使如此,它们也不必绑定吗?

编辑 (6-29-16)
在 AndroidStudio 更新后,我似乎无法重现该错误。它按我的预期工作,绑定空视图。如果这个问题再次出现,我会回到这个帖子。

【问题讨论】:

  • 像你一样选择你的行不是一个好习惯(而且它不起作用),更好的方法是有一个选择项目的列表(或地图),然后在onBind检查位置是是否存在于您的列表中。如果存在,则将行更改为选中,否则显示正常行。
  • 这就是我所拥有的。但是,如果您只在onBind 中更新它们,则用户将看不到可见项目的更改,直到它们再次绑定(可能是onResume 或滚动)。我省略了多余的代码,以免使问题复杂化。虽然我想找到一种比我和你提到的更清洁的方法。
  • 您可以拨打notifyDataSetChangednotifyItemChanged手动拨打onBind
  • @shayanpourvatan 谢谢,但我认为这不是一个好的解决方案。在文档中指出调用notifyDataSetChanged() 作为最后的结果。并且要使用其中一种更细粒度的方法,我需要知道哪些不是每次都绑定的,这不是一个常数。无论哪种方式,它都不能真正解决问题,它只是解决它。
  • 我遇到了同样的问题,似乎我将 setHasStableIds 选项设置为 true。

标签: android android-recyclerview android-viewholder


【解决方案1】:

发生这种情况是因为:

  • 视图未添加到 recyclerview 中(getChildAt 将不起作用,并且将为该位置返回 null)
  • 它们也被缓存(onBind 不会被调用)

致电recyclerView.setItemViewCacheSize(0) 将解决这个“问题”。

由于默认值为 2(RecyclerView.Recycler 中的private static final int DEFAULT_CACHE_SIZE = 2;),您将始终获得 2 个不会调用 onBind 但未添加到回收器的视图

【讨论】:

  • recyclerView.setItemViewCacheSize(0) 不是一个好的做法,因为我们失去了性能改进。
  • 是的,我同意@TinTran。请参阅上面的编辑并感谢您的贡献。
  • 这只是解释为什么会发生这种情况。升级 AndroidStudio 改变了自 recyclerview 出现以来正在发生的行为,这似乎很奇怪。
  • 这是我的问题的正确答案。这个问题在这里得到了很好的描述link,这有助于很好的解释。
【解决方案2】:

在您的情况下,位置 8 和 9 的视图没有被回收,它们正在从窗口中分离出来,并将再次附加。对于这些分离的视图onBindViewHolder 没有被调用,只有onViewAttachedToWindow 被调用。如果您在适配器中覆盖这些功能,您可以看到我在说什么。

@Override
    public void onViewRecycled(ViewHolder vh){
        Log.wtf(TAG,"onViewRecycled "+vh);
    }

    @Override
    public void onViewDetachedFromWindow(ViewHolder viewHolder){
        Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder);
    }

现在,为了解决您的问题,您需要跟踪应该回收但被分离的视图,然后在

上执行您的部分处理
@Override
    public void onViewAttachedToWindow(ViewHolder viewHolder){
        Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder);
    }

【讨论】:

  • 请参阅上面的编辑,感谢您的贡献。
【解决方案3】:

我认为在 recyclerview 中玩视图不是一个好主意。我一直使用的方法只是在用于 RecyclerView 的模型中引入一个标志。假设你的模型是这样的 -

class MyModel{
    String name;
    int age;
}

如果您正在跟踪是否选择了视图,则将一个布尔值引入模型。现在它看起来像 -

class MyModel{
    String name;
    int age;
    boolean isSelected;
}

现在您的复选框将根据新标志 isSelected(在 onBindViewHolder() 中)被选中/取消选中。在视图上的每个选择都会将相应模型选定值的值更改为 true,并且在未选择时将其更改为 false。在您的情况下,只需运行一个循环将所有模型的 isSelected 值更改为 true,然后调用 notifyDataSetChanged()

例如,假设您的列表是

ArrayList<MyModel> recyclerList;
private void selectAll(){
    for(MyModel myModel:recyclerList)
        myModel.isSelected = true;
    notifyDataSetChanged();
}

我的建议是,在使用 recyclerView 或 ListView 时,尽量少玩视图。

所以在你的情况下 -

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
   holder.clickableView.setTag(position);
   holder.selectableView.setTag(position);
   holder.checkedView.setChecked(recyclerList.get(position).isSelected);
    Log.d(TAG, "onBindViewHolder() position: " + position);
    ...
}

@Override
public void onClick(View view){
    int position = (int)view.getTag();
    recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected;
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      int position = (int)buttonView.getTag();
      recyclerList.get(position).isSelected = isChecked;
}

希望对您有所帮助,如果您需要任何进一步的解释,请告诉我:)

【讨论】:

  • 这会起作用,除非用户选择了一个可见的视图,因此已经绑定了?
  • 在 onBindViewHolder() 中将位置与每个可点击视图(使用 setTag)绑定。因此,每当您收到 onClick 或 onChangeListner 的回调时,只需获取标记的位置并在相应的模型中进行更改。
  • 我很抱歉,但我不确定我是否关注
  • @YoungCoconutCode 我再重复一遍,总是尝试使用您的数据结构而不是在适配器中播放视图,这是最佳代码实践之一。因为适配器总是重用视图,在这种情况下,任何未连接到您的数据结构的东西都将在重用特定视图的情况下重复或丢失。
  • 不,我明白了,我同意这会清理我的代码很多,但我不清楚你建议如何更新已经通过 onBind 的已绑定视图因此在被选中后不会检查isSelected()
【解决方案4】:

所以我认为@Pedro Oliveira 在下面回答了您的问题。 RecycleView 的主要意义,就是他使用特殊的算法来随时缓存 ViewHolder。所以下一个 onBindViewHolder(...) 可能不起作用,例如。如果视图是静态的,或者其他的。

关于您认为将 RecycleView 用于动态更改视图的问题。 不要这样做! 因为 RecycleView 使视图无效并且有缓存系统,所以你会遇到很多问题。

使用LinkedListView 完成此任务!

【讨论】:

    【解决方案5】:

    Pedro Oliveira 和 Zartha 的回答非常有助于理解问题,但我没有看到任何令我满意的解决方案。

    我相信你有 2 个不错的选择,具体取决于你在做什么:

    选项 1

    如果您希望 onBindViewHolder() 被调用以获得屏幕外视图,无论它是否被缓存/分离,那么您可以这样做:

    RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition( some_position );
    
    if ( view_holder != null )
    {
        //manipulate the attached view
    }
    else //view is either non-existant or detached waiting to be reattached
        notifyItemChanged( some_position );
    

    这个想法是,如果视图被缓存/分离,那么notifyItemChanged() 将告诉适配器视图无效,这将导致onBindViewHolder() 被调用。

    选项 2

    如果您只想执行部分更改(而不是onBindViewHolder() 中的所有内容),那么在onBindViewHolder( ViewHolder view_holder, int position ) 中,您需要将position 存储在view_holder 中,并执行您想要的更改onViewAttachedToWindow( ViewHolder view_holder )

    为了简单起见,我推荐选项 1,除非您的 onBindViewHolder() 正在做一些密集的事情,比如搞乱位图。

    【讨论】:

    • 选项 1 帮助了我。谢谢!
    【解决方案6】:

    当您传递给recyclerview adapter的列表中有大量项目时,您将不会遇到onBindViewHolder()在滚动时不执行的问题。

    但如果列表中的项目较少(我已检查列表大小 5),您可能会遇到此问题。

    更好的解决方案是检查list size

    请在下面找到示例代码。

    private void setupAdapter(){
        if (list.size() <= 10){
            recycler.setItemViewCacheSize(0);
         }
         recycler.setAdapter(adapter);
         recycler.setLayoutManager(linearLayoutManager);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-21
      • 2015-08-03
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多