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 метод, так что если вы изменили что-либо из этого держателя, вам придется его сбросить. Это произойдет, если вы измените фон, положение элемента и т. Д. Любое изменение, не относящееся к контенту как таковое, должно быть сброшено в этом методе.

Другие вопросы по тегам