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.