RecyclerView вызывает проблемы при утилизации
У меня есть список предметов, которые я создал, используя RecyclerView
, Когда пользователь нажимает на одну из них, я меняю цвет фона выбранного элемента. Проблема в том, что когда я прокручиваю свои предметы, и они перерабатываются, некоторые предметы получают цвет фона выбранного предмета (что неправильно). Здесь вы можете увидеть мой Adapter
код:
public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.ViewHolder> {
private static final String SELECTED_COLOR = "#ffedcc";
private List<OrderModel> mOrders;
public OrderAdapter() {
this.mOrders = new ArrayList<>();
}
public void setOrders(List<OrderModel> orders) {
mOrders = orders;
}
public void addOrders(List<OrderModel> orders) {
mOrders.addAll(0, orders);
}
public void addOrder(OrderModel order) {
mOrders.add(0, order);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.order_main_item, parent, false);
// Return a new holder instance
ViewHolder viewHolder = new ViewHolder(contactView);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
final OrderModel orderModel = mOrders.get(position);
// Set item views based on the data model
TextView customerName = viewHolder.customerNameText;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss:S");
String time = simpleDateFormat.format(orderModel.getOrderTime());
customerName.setText(time);
TextView orderNumber = viewHolder.orderNumberText;
orderNumber.setText("Order No: " + orderModel.getOrderNumber());
Button button = viewHolder.acceptButton;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewHolder.userActions.acceptButtonClicked(position);
}
});
final LinearLayout orderItem = viewHolder.orderItem;
orderItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewHolder.userActions.itemClicked(orderModel);
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
}
});
}
@Override
public int getItemCount() {
return mOrders.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements OrderContract.View {
public TextView customerNameText;
public Button acceptButton;
public TextView orderNumberText;
public OrderContract.UserActions userActions;
public LinearLayout orderItem;
public ViewHolder(View itemView) {
super(itemView);
userActions = new OrderPresenter(this);
customerNameText = (TextView) itemView.findViewById(R.id.customer_name);
acceptButton = (Button) itemView.findViewById(R.id.accept_button);
orderNumberText = (TextView) itemView.findViewById(R.id.order_number);
orderItem = (LinearLayout) itemView.findViewById(R.id.order_item_selection);
}
@Override
public void removeItem() {
}
}
3 ответа
Проблема в recyclerView
поведение утилизации, которые присваивают вам вне экрана ViewHolder
предметы для новых предметов, которые будут отображаться на экране. Я бы не советовал вам связывать свою логику на основе ViewHolder
возражать, как во всех ответах выше. Это действительно вызовет у вас проблемы. Вы должны строить логику на основе состояния вашего объекта данных, а не ViewHolder
Объект, как вы никогда не будете знать, когда он будет переработан.
Предположим, вы сохраняете логическое состояние isSelected в ViewHolder
чтобы проверить, но и если это правда, то это же состояние будет там для нового элемента, когда это viewHolder
будет переработан.
Лучший способ сделать это - сохранить любое состояние в объекте DataModel. В вашем случае просто логический isSelected.
Пример примера как
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<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
public MyRecyclerViewAdapter(Context context, List<DataModel> 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);
}
}
}
Как @Gabriel спросил в комментарии,
Что делать, если вы хотите выбрать один элемент за раз?
В этом случае, опять же, не следует сохранять состояние выбранного элемента в ViewHolder
объект, так же как он перерабатывается и вызывает у вас проблемы. Для этого лучше иметь поле int selectedItemPosition
в Adapter
класс не ViewHolder
, Следующий фрагмент кода покажет это.
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private Context mContext;
private List<DataModel> mRViewDataList;
//variable to hold selected Item position
private int mSelectedItemPosition = -1;
public MyRecyclerViewAdapter(Context context, List<DataModel> 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);
}
}
}
Если вам нужно только поддерживать выбранное состояние элемента, я настоятельно не рекомендую использовать метод notifyDataSetChanged() класса Adapter, поскольку RecyclerView обеспечивает гораздо большую гибкость для этих случаев.
Вы должны изменить свою логику, назначив значение внутри элемента (объекта), а не представления:
orderItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
orderItem.setSelected(xxxx);
}
});
Тогда в вашем onBindViewHolder
Метод, который вы должны присвоить цвет в соответствии с этим значением в элементе.
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(xxxx);
} else {
viewHolder.orderItem.setBackgroundColor(xxxx);
}
Это довольно распространенная ошибка, которая имеет простое решение.
Быстрый ответ: добавьте эту строку в свой onBindViewHolder
метод:
if (orderItem.isSelected()){
viewHolder.orderItem.setBackgroundColor(Color.parseColor(SELECTED_COLOR));
} else {
viewHolder.orderItem.setBackgroundColor(Color.parseColor(DEFAULT_COLOR));
}
(с DEFAULT_COLOR
цвет, который имеет видоискатель по умолчанию)
Объясненный ответ: когда система перезагружает видоискатель, она просто вызывает onBindViewHolder
метод, так что если вы изменили что-либо из этого держателя, вам придется его сбросить. Это произойдет, если вы измените фон, положение элемента и т. Д. Любое изменение, не относящееся к контенту как таковое, должно быть сброшено в этом методе.