【问题标题】:Cannot call this method while RecyclerView is computing a layout or scrolling when try remove item from recyclerview当 RecyclerView 正在计算布局或滚动尝试从 recyclerview 中删除项目时无法调用此方法
【发布时间】:2017-04-05 04:30:36
【问题描述】:

我正在尝试从 recyclerview 中删除我的项目,但我总是收到错误

java.lang.IllegalStateException:无法调用此方法,而 RecyclerView 正在计算布局或滚动

我正在使用通知数据集已更改,我可以解决这个问题吗?

public class AdapterIntransit extends RecyclerView.Adapter<AdapterIntransit.ViewHolder> {
    private Context context;
    List<DataIntransit> data;

    public AdapterIntransit(Context context, List<DataIntransit> data) {
        this.context = context;
        this.data = data;
    }

    @Override
    public AdapterIntransit.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardintransit, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(AdapterIntransit.ViewHolder holder, int position) {
        if (data.get(position).getJml1() - data.get(position).getJml2() <= 0) {
            data.remove(position);
            notifyItemRemoved(position);
            notifyItemRangeChanged(position, getItemCount());
            notifyDataSetChanged();
        } else {
            holder.kode.setText(data.get(position).getKode());
            holder.nama.setText(data.get(position).getNama());
            holder.jumlah.setText(String.valueOf(data.get(position).getJml1() - data.get(position).getJml2()));
        }
    }

    @Override
    public int getItemCount() {
        return data.size();
    }
    public class ViewHolder extends RecyclerView.ViewHolder{
        TextView kode, nama, jumlah;
        public ViewHolder(View itemView) {
            super(itemView);
            kode = (TextView)itemView.findViewById(R.id.kode);
            nama = (TextView)itemView.findViewById(R.id.nama);
            jumlah = (TextView)itemView.findViewById(R.id.jumlah);

        }
    }
}

【问题讨论】:

标签: android android-recyclerview


【解决方案1】:

以下答案对我有用

这只是workaround 的问题。

这通常发生在您在background thread 上调用notifyDataSetChanged() 时。所以把通知移到UI thread

recyclerView.post(new Runnable()
            {
              @Override
              public void run() {
                myadapter.notifyDataSetChanged();
              }
            });

您使用您的 RecyclerView 实例并在 post 方法中将一个新的 Runnable 添加到消息队列中。 runnable 将在用户界面线程上运行。这是 Android 从后台访问 UI 线程的限制(例如,在将在后台线程中运行的方法内)。 如果需要,您可以在 UI 线程上运行它。

如果需要,您可以在 UI 线程上运行它

 runOnUiThread(new Runnable(){
 public void run() {
      // UI code goes here
 }
 });

【讨论】:

  • 这似乎是 RecyclerView 库中的一个错误,当notifyDataSetChanged() 被快速连续调用(在 50 毫秒内)或在滚动时——即使它是在 onBindViewHolder() 之外调用的。例如,当使用notifyDataSetChanged() 刷新整个列表时会发生这种情况,而不必添加或删除项目。 Runnable 建议效果很好。这里有一个错误报告:issuetracker.google.com/issues/37136189
  • 一般来说,为 UI 做一些依赖时间的事情是个坏主意。这会起作用,但它是一个闩锁而不是解决方案。
  • 一个原因可能是您的视图持有者中有一个动画视图。可能是切换,切换后你调用notify...
  • @Sermilion :是的,我有一个开关和切换它,我做了一些操作然后通知。上述解决方案是否适用?
  • @AndroidGuy 这会起作用,但这不是正确的方法。如果您有开关,并且您手动切换它,请不要通知。开关将自行制作动画,您就完成了。如果你想以编程方式改变switch的状态,那么你就通知,通知后switch就会改变。
【解决方案2】:

这在点击 recyclerview 中的顶部复选框时选择所有复选框时很有用。

recyclerview.post(new Runnable()
{
     @Override
     public void run() {
         myadapter.notifyDataSetChanged();
     }
});

【讨论】:

    【解决方案3】:

    如果您想通知回收项目,请使用 isComputingLayout 方法:

    if (!mRecyclerView.isComputingLayout()) 
    {
     // add your code here
    }
    

    【讨论】:

    • 如果是计算布局,那么来自代码的请求永远不会完成?
    【解决方案4】:

    您需要两种方法才能使其工作:

    if (!rv.isComputingLayout && rv.scrollState == SCROLL_STATE_IDLE) {
       rv.adapter?.notifyDataSetChanged()
    }
    

    【讨论】:

      【解决方案5】:

      当您的项目正在构建时,您将在onBindViewHolder() 方法中通知项目更改。很可能你可以改进你的逻辑,以避免这种情况。

      【讨论】:

        【解决方案6】:

        我给你另一个想法来解决你的问题。我认为这可能会更好 我们的想法是我们不会删除onBindViewHolder 中的无效数据,我们会在之前删除它

        public AdapterIntransit(Context context, List < DataIntransit > data) {
            this.context = context;
            this.data = removeInValidData(data);
        }
        
        private void removeInValidData(List < DataIntransit > data) {
            for (int position = 0, position < data.size(); position++) {
                if (data.get(position).getJml1() - data.get(position).getJml2() <= 0) {
                    data.remove(position);
                }
            }
        }
        
        @Override
        public void onBindViewHolder(AdapterIntransit.ViewHolder holder, int position) {
            holder.kode.setText(data.get(position).getKode());
            holder.nama.setText(data.get(position).getNama());
            holder.jumlah.setText(String.valueOf(data.get(position).getJml1() - data.get(position).getJml2()));
        
        }
        

        【讨论】:

        • 谢谢您的回答,但我无法从那里访问位置。
        • 只是代码而不知道为什么代码可以解决问题并不是很有帮助。还请解释您的解决方案背后的想法。
        • @Janusz 谢谢。更新了答案。我曾经认为如果人们阅读问题+检查日志+检查我的答案,人们就会明白这个答案
        • 此解决方案仅在您想在构建适配器时过滤数据时才有效
        【解决方案7】:

        recycler view在计算layout时,如果我们尝试通知item变化或者数据变化,就会出现这个问题。但是根据官方文档,它是这样写的

        您的代码不太可能在 state(isComputingLayout) 期间运行,因为当布局遍历发生或 RecyclerView 开始滚动以响应系统事件(触摸、可访问性等)时,框架会调用它。

        但有时会发生此问题导致崩溃。因此,最好按照以下方式进行操作。

        if (isComputingLayout(recyclerView).not()) {
             if (Looper.myLooper() != Looper.getMainLooper()) {
                  // If BG thread,then post task to recycler view
                 recyclerView?.post { notifyItemChanged(position, obj) }
             } else {
                 notifyItemChanged(position, obj)
             }
        }
        

        【讨论】:

          【解决方案8】:

          我只是像下面这样设置延迟

                  new Handler().postDelayed(() ->  notifyDataSetChanged(), 1000);
          

          【讨论】:

          • 处理程序不是最好的解决方案
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多