RecyclerView.Adapter的onBindViewHolder()方法获取的位置不正确。

13
我将展示代码以及获取问题的步骤。
我在选项卡片段中有一个RecyclerView,它从自定义对象中获取数据集:
mRecyclerView = (RecyclerView) v.findViewById(R.id.recyclerview);

mRecyclerView.setLayoutManager(mLayoutManager);

mRecyclerAdapter = new MyRecyclerAdapter(mMes.getListaItens(), this, getActivity());

mRecyclerView.setAdapter(mRecyclerAdapter);

我在适配器的onBindViewHolder()方法中设置了列表项的长按行为:

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {

    ItemMes item = mListaItens.get((position));

    holder.descricao.setText(item.getDescrição());
    holder.valor.setText(MainActivity.decimalFormatWithCod.format(item.getValor()));

    ...

    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {

            new MaterialDialog.Builder(mContext)
                    .title(holder.descricao.getText().toString())
                    .items(R.array.opcoes_longclick_item)
                    .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() {
                        @Override
                        public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {

                            switch (which) {
                                case 0:
                                    mParentFragment.showUpdateItemDialog(position);
                                    return true;

                                case 1:
                                    mParentFragment.showDeleteItemDialog(position);
                                    return true;
                            }

                            return false;
                        }
                    })
                    .show();

            return true;
        }
    });

}

然后,片段中负责删除项目本身的方法:
public void showDeleteItemDialog(int position) {

    final ItemMes item = mMes.getListaItens().get(position);

    new MaterialDialog.Builder(getActivity())
            .title("Confirmar Remoção")
            .content("Tem certeza que deseja remover " + item.getDescrição() + "?")
            .positiveText("Sim")
            .negativeText("Cancelar")
            .onPositive(new MaterialDialog.SingleButtonCallback() {
                @Override
                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                    deleteItem(item);
                }
            })
            .show();

}

public void deleteItem(ItemMes item) {

    getMainActivity().deleteItemFromDatabase(item.getID());

    int position = mMes.getListaItens().indexOf(item);

    mMes.getListaItens().remove(position);

    mRecyclerAdapter.notifyItemRemoved(position);

    atualizaFragment();

}

最后,在活动中执行数据库操作的方法:

 public int deleteItemFromDatabase(long id) {

    SQLiteDatabase db = dataBaseHelper.getWritableDatabase();

    String where = DBHelper.COLUNA_ID + " = ?";

    String[] args = {String.valueOf(id)};

    int rowsAffected = db.delete(DBHelper.TABELA_ITEM, where, args);

    db.close();

    return rowsAffected;

}

现在我将重现以下步骤:我在列表视图中显示了3个项目。然后我尝试删除第一个:
1- 长按被拦截并传递正确的索引: enter image description here 2- 该项已从数据库中正确删除: enter image description here 3- 在所有这些操作之后,正如预期的那样,适配器存储并显示2个项目... enter image description here 如果我尝试删除这个包含两个项目的列表的第一个项目,我会得到错误的位置(应该是0,但是是1): The position = 1 而且,如果我尝试删除这个包含两个项目的列表的最后一个项目,我会得到错误的位置(应该是1,但是是2): enter image description here 问题是:如果我有一个大小为2的数据集(并且适配器知道它),它如何调用onBindViewHolder(ViewHolder holder, int [last index +1])enter image description here 我不知道出了什么问题。所以我请求帮助,因为我认为放弃这个项目,因为我做的一切都是正确的,但总是有些事情不能正常工作,让我感到疲倦。 提前致谢。

你需要同时包含适配器代码。这很可能是因为您的适配器没有正确处理“删除”操作。它需要减少其总数。如果您使用的是简单列表,其中ID就是位置,那么您还将遇到需要重新创建视图的问题。 - Larry Schiefer
没有看到完整的代码,很难确定。 - Larry Schiefer
告诉我你想要哪一部分,我会发布它。如果我把所有内容都贴出来会很糟糕。也许我可以在Bitbucket上粘贴一些文件URL... - Informatheus
适配器和视图持有者的代码是最关键的部分。如果它太大或太复杂,我建议创建一个更小的项目(甚至是不同的视图),只做你需要演示问题的最少量。这样做可能会更容易帮助那些试图提供帮助的人,并且甚至可以让你更轻松地了解正在发生的事情。 - Larry Schiefer
http://pastebin.com/E44CcM7U - Informatheus
4个回答

17

我注意到在方法onBindViewHolder(VH holder, int position)中,当位置出错时,holder.getAdapterPosition()总是给出正确的位置。

所以我将我的代码更改为:

ItemMes item = mListaItens.get((position));

...

mParentFragment.showUpdateItemDialog(position);

...

mParentFragment.showDeleteItemDialog(position);

....

To:

:收件人:
 ItemMes item = mListaItens.get((holder.getAdapterPosition()));

...

mParentFragment.showUpdateItemDialog(holder.getAdapterPosition());

...

mParentFragment.showDeleteItemDialog(holder.getAdapterPosition());

....

一切都运行良好。这很奇怪,但是...... 谢谢大家。


4
看了一下你在评论中提供的适配器代码,它非常简单明了。试试这个方法:不要调用notifyItemRemoved(),而是调用notifyDataSetChanged()。虽然这样做相当耗费资源,因为它会导致适配器重新绑定数据集并重新创建ViewHolders,但由于你正在使用一个ArrayList,在删除元素时,这确实是最简单的方法。否则,你将不得不跟踪项目的位置,并且当一个项目被删除时,它不能改变其他项目的位置 - 或者处理项目在数据集中移动位置的情况。

我可能在这里遇到了类似的问题:http://stackoverflow.com/questions/43531900/android-how-to-fix-recyclerview-onbindviewholder。我会感激任何关于如何修复的想法或建议。 - AJW

3
根据RecyclerView的getAdapterPosition文档:
RecyclerView在下一个布局遍历之前不处理任何适配器更新。这可能会在用户看到屏幕上的内容和适配器内容之间创建临时不一致。由于这种不一致性小于16ms,因此它并不重要,但如果您想使用ViewHolder位置来访问适配器,则可能会有问题。有时,您可能需要获取确切的适配器位置以对用户事件做出反应。在这种情况下,您应该使用此方法,它将计算ViewHolder的Adapter位置。 因此,在实现用户事件的情况下,使用getAdapterPosition是推荐的方法。请记住保留html标签。

2

在onBindViewHolder()中尝试使用以下代码:

int adapterPos=holder.getAdapterPosition();
        if (adapterPos<0){
            adapterPos*=-1;
        }

ItemMes item = mListaItens.get((adapterPos));
mParentFragment.showUpdateItemDialog(adapterPos);

使用adapterPos代替position变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接