Skip to content

La RecyclerView causa un problema durante il riciclo

Ciao, abbiamo la risposta a ciò di cui hai bisogno, scorri e lo vedrai qui.

Soluzione:

Il problema è recyclerView comportamento di riciclaggio che assegna il vostro fuori schermo ViewHolder a nuovi elementi che verranno visualizzati sullo schermo.
Non vi suggerirei di legare la vostra logica basata su ViewHolder come in tutte le risposte precedenti. Vi causerà davvero dei problemi.
Si dovrebbe costruire la logica in base allo stato dell'oggetto dati, non in base all'oggetto ViewHolder perché non si saprà mai quando verrà riciclato.

Supponiamo di salvare uno
stato booleano isSelected in ViewHolder per controllare, ma se è vero, allora lo stesso stato sarà presente per il nuovo elemento, quando questo viewHolder sarà riciclato.

Il modo migliore per fare quanto sopra è mantenere lo stato qualsiasi nell'oggetto DataModel. Nel vostro caso è sufficiente un oggetto booleano isSelected.

Esempio di esempio come

package chhimwal.mahendra.multipleviewrecyclerproject;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.CardView;
import android.widget.TextView;

import java.util.List;

/**
 * Created by mahendra.chhimwal on 12/10/2015.
 */
public class MyRecyclerViewAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List mRViewDataList;

    public MyRecyclerViewAdapter(Context context, List rViewDataList) {
        this.mContext = context;
        this.mRViewDataList = rViewDataList;
    }

    @Override
    public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bindDataWithViewHolder(mRViewDataList.get(position));
    }

    @Override
    public int getItemCount() {
        return mRViewDataList != null ? mRViewDataList.size() : 0;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private TextView textView;
        private LinearLayout llView;
        private DataModel mDataItem=null;

        public ViewHolder(View itemView) {
            super(itemView);
            llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
            textView = (TextView) itemView.findViewById(R.id.tvItemName);
            cvItemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                  // One should handle onclick of event here based on the dataItem i.e. mDataItem in this case.
                  // something like that..
                /* Intent intent = new Intent(mContext,ResultActivity.class);
                 intent.putExtra("MY_DATA",mDataItem);   //If you want to pass data.
                 intent.putExtra("CLICKED_ITEM_POSTION",getAdapterPosition()); // If one want to get selected item position
                 startActivity(intent);*/
                 Toast.makeText(mContext,"You clicked item number "+ViewHolder.this.getAdapterPosition(),Toast.LENTH_SHORT).show();
                }
            });
        }

        //This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
        //Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
        public void bindDataWithViewHolder(DataModel dataItem){
            this.mDataItem=dataItem;

            if(mDataItem.isSelected()){
                llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
            }else{
                llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
            }
            //other View binding logics like setting text , loading image  etc.
            textView.setText(mDataItem);
        }
    }
}

Come chiesto da @Gabriel in un commento,

cosa succede se si vuole selezionare un singolo elemento alla volta?

In questo caso, ancora una volta non si dovrebbe salvare lo stato dell'elemento selezionato in ViewHolder perché lo stesso viene riciclato e causa problemi. Per questo, il modo migliore è avere un campo int selectedItemPosition in Adapter classe non ViewHolder .
Il seguente frammento di codice lo mostra.

public class MyRecyclerViewAdapter extends RecyclerView.Adapter {

        private Context mContext;
        private List mRViewDataList;

        //variable to hold selected Item position
        private int mSelectedItemPosition = -1;

        public MyRecyclerViewAdapter(Context context, List rViewDataList) {
            this.mContext = context;
            this.mRViewDataList = rViewDataList;
        }

        @Override
        public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
            return new ViewHolder(view);
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            holder.bindDataWithViewHolder(mRViewDataList.get(position),position);
        }

        @Override
        public int getItemCount() {
            return mRViewDataList != null ? mRViewDataList.size() : 0;
        }

        public class ViewHolder extends RecyclerView.ViewHolder {
            private TextView textView;
            private LinearLayout llView;
            private DataModel mDataItem=null;

            public ViewHolder(View itemView) {
                super(itemView);
                llView=(LinearLayout)itemView.findViewById(R.id.ll_root_view);
                textView = (TextView) itemView.findViewById(R.id.tvItemName);
                cvItemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //Handling for background selection state changed
                        int previousSelectState=mSelectedItemPosition;
                        mSelectedItemPosition = getAdapterPosition();
                        //notify previous selected item
                        notifyItemChanged(previousSelectState);
                        //notify new selected Item
                        notifyItemChanged(mSelectedItemPosition);

                        //Your other handling in onclick

                    }
                });
            }

            //This is clean method to bind data with viewHolder. Do all dirty things on View based on dataItem.
            //Must be called from onBindViewHolder(),with dataItem. In our case dataItem is String object.
            public void bindDataWithViewHolder(DataModel dataItem, int currentPosition){
                this.mDataItem=dataItem;
                //Handle selection  state in object View.
                if(currentPosition == mSelectedItemPosition){
                    llView.setBackgroundColor(Color.ParseColor(SELCTED_COLOR);
                }else{
                    llView.setBackgroundColor(Color.ParseColor(DEFAULT_COLOR);
                }
                //other View binding logics like setting text , loading image  etc.
                textView.setText(mDataItem);
            }
        }
    }

Se si deve mantenere solo lo stato dell'elemento selezionato, sconsiglio vivamente l'uso di notifyDataSetChanged() della classe Adapter, in quanto RecyclerView offre molta più flessibilità per questi casi.

Si dovrebbe modificare la logica per assegnare il valore all'interno dell'elemento (oggetto) e non alla vista:

orderItem.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
           orderItem.setSelected(xxxx);
        }
    });

Quindi nel metodo onBindViewHolder si deve assegnare il colore in base a questo valore nell'oggetto.

if (orderItem.isSelected()){
   viewHolder.orderItem.setBackgroundColor(xxxx);
} else {
  viewHolder.orderItem.setBackgroundColor(xxxx);
}

Questo è un errore abbastanza comune che ha una facile soluzione.

Risposta rapida: aggiungere questa riga nel metodo onBindViewHolder del metodo:

if (orderItem.isSelected()){
    viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
} else {
    viewHolder.orderItem.setBackgroundColor(Color.parseColor(DEFAULT_COLOR));
}

(con DEFAULT_COLOR il colore che il viewholder ha di default)

Risposta spiegata: quando il sistema ricicla un viewholder chiama semplicemente onBindViewHolder quindi se si è modificato qualcosa di quel viewholder, si dovrà ripristinarlo. Ciò accade se si cambia lo sfondo, la posizione dell'elemento, ecc. Qualsiasi modifica che non sia legata al contenuto in sé dovrebbe essere reimpostata con questo metodo.

Valutazioni e commenti

Alla fine di questo articolo puoi trovare le interpretazioni di altri amministratori di sistema, hai anche la libertà di mostrare la tua se lo ritieni opportuno.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.