web-dev-qa-db-ja.com

RecyclerView.Adapter onBindViewHolder()が間違った位置を取得する

問題を取得するためのコードと手順の後に表示します

カスタムオブジェクトからデータセットを取得するタブ付きフラグメント内に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();

}

そして最後に、DB操作を行うアクティビティ内のメソッド:

 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-longclickは正しいインデックスを渡して傍受されます: enter image description here

2-アイテムはデータベースから正しく削除されます: enter image description here

3-結局のところ、予想通り、アダプターは2つのアイテムを格納して表示しています... enter image description here

SO、この2項目リストの最初の項目を削除しようとすると、間違った位置が表示されます(0である必要があり、1です):The position = 1

また、この2つのアイテムリストの最後のアイテムを削除しようとすると、間違った位置が表示されます(1である必要があります2です):enter image description here

問題は、私がサイズ2のデータセットを持っている(そしてアダプターがそれを知っている)場合、onBindViewHolder(ViewHolderholder、int [last index +1])を呼び出すにはどうすればよいですか? enter image description here

何が悪いのか私にはわかりません。だから私はこのプロジェクトをあきらめることを考えているので助けを求めます私はすべてを正しく行いますが常に何かがうまくいかないので、私は疲れています。 よろしくお願いします。

11
Informatheus

位置が間違っていたときにメソッドonBindViewHolder(VHholder、int position)で、holder.getAdapterPosition()が常に正しい位置を提供していることに気付きました。

だから私は私のコードを次のように変更しました:

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

...

mParentFragment.showUpdateItemDialog(position);

...

mParentFragment.showDeleteItemDialog(position);

....

に:

 ItemMes item = mListaItens.get((holder.getAdapterPosition()));

...

mParentFragment.showUpdateItemDialog(holder.getAdapterPosition());

...

mParentFragment.showDeleteItemDialog(holder.getAdapterPosition());

....

そして、すべてがうまくいきます。これは非常に奇妙ですが...みんなありがとう。

13
Informatheus

コメントで提供したアダプターコードを見てみると、かなり簡単です。これを試してください:notifyItemRemoved()を呼び出すのではなく、notifyDataSetChanged()を呼び出します。アダプターがデータセットを再バインドする(そしてViewHoldersを再作成する)ため、これはかなりコストがかかりますが、要素を削除する場所でArrayListを使用しているため、それは本当にそれを行う最も簡単な方法です。それ以外の場合は、アイテムの位置を追跡する必要があります。アイテムが削除されると、他のアイテムの位置を変更できません。または、アイテムがデータセット内で位置をシフトする場合に対処できます。

3
Larry Schiefer

RecyclerViewgetAdapterPositionドキュメントによると:

RecyclerViewは、次のレイアウトトラバーサルまでアダプターの更新を処理しません。これにより、ユーザーが画面に表示するものとアダプタの内容との間に一時的な不整合が生じる可能性があります。この不整合は16ms未満になるため重要ではありませんが、ViewHolder位置を使用してアダプターにアクセスする場合は問題になる可能性があります。 ユーザーイベントに応じてアクションを実行するために、正確なアダプター位置を取得する必要がある場合があります。その場合、ViewHolderのアダプター位置を計算するこのメソッドを使用する必要があります。

したがって、ユーザーイベントを実装する場合は、getAdapterPositionを使用することをお勧めします。

1
Tom11

このコードをonBindViewHolder()で試してください

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

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

位置変数の代わりにadapterPosを使用します。

0