2016-02-08 2 views
7

У меня есть список элементов, которые я создал с помощью RecyclerView. Когда пользователь нажимает на один из них, я меняю цвет фона этого выбранного элемента. Проблема в том, что когда я просматриваю свои предметы, и они перерабатываются, некоторые элементы получают цвет фона выбранного элемента (что неверно). Здесь вы можете увидеть код моего адаптера:RecyclerView вызывает проблемы при утилизации

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() { 

    } 
} 
+0

Вы должны поддерживать, какой элемент выбран на основе заданного цвета фона –

ответ

7

Проблема заключается в том recyclerView поведении рециркуляции, которые назначают из экрана ViewHolder элементов для новые предметы, которые будут отображаться на экране. Я бы не предложил вам привязать свою логику на основе объекта ViewHolder, как и во всех вышеперечисленных ответах. Это действительно вызовет у вас проблемы. Вы должны построить логику, основанную на состоянии вашего объекта данных, а не ViewHolder Объект, который вы никогда не узнаете, когда он будет переработан.

Предположим, вы сохраните состояние булево IsSelected в ViewHolder, чтобы проверить, но если это правда, то же самое состояние будет для нового пункта, когда это viewHolder будут переработаны.

Лучше всего сделать выше, удерживая любое состояние в объекте DataModel. В вашем случае выбрано только boolean.

Sample пример как

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() метода класса адаптера, как RecyclerView обеспечивает большую гибкость для этих случаев.

+0

Я отметил ваш ответ как правильный, я не думал об этом полностью, когда я это делал. Но есть проблема с этим, в вашем операторе if, где вы проверяете, является ли mDataItem.isSelected() истинным, это всегда так, как только вы выберете элемент, который он будет выбран навсегда. Но вам нужно отменить выбор, если выбран другой элемент! – Gabriel

+0

@Gabriel, если вы хотите выбрать только один элемент за один раз, например, навигация Ящик вообще делает это другим. Вопрос не задает требование единого выбора за раз. Вы можете справиться с этим очень изящно, хотя. Пожалуйста, ознакомьтесь с моим обновленным ответом. –

+0

@Gabriel дайте мне знать, если он решит вашу проблему или нет? –

1

Вы должны изменить вашу логику присвоить значение внутри элемента (объекта) не вид:

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); 
} 
+0

Я дал вам положительный ответ, я не думал об этом полностью, когда я это делал. Но есть проблема с этим, в вашем операторе if, где вы проверяете, является ли mDataItem.isSelected() истинным, это всегда так, как только вы выберете элемент, который он будет выбран навсегда. Но вам нужно отменить выбор, если выбран другой элемент! – Gabriel

+0

@Gabriel Ответ объясняет, почему у вас проблема при прокрутке. Конечно, он не может решить все ваши дела. Если вам нужно отменить выбор элемента, просто измените свой логин на clickListener. Отключите другие элементы или просто сохраните идентификатор (а не позицию) выбранного элемента. –

1

Это довольно распространенная ошибка, которая имеет простое решение.

Быстрый ответ: добавьте эту строку в onBindViewHolder метод:

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

DEFAULT_COLOR цвет, который viewholder имеет по умолчанию)

Разъяснения ответ: когда система регенерирует viewholder он просто называет onBindViewHolder, поэтому, если вы изменили что-либо из этого зрителя, вам придется его сбросить. Это произойдет, если вы измените фон, положение элемента и т.д. Любые изменения, не связанные с содержанием самих по себе должны быть сброшено в этом методе

Смежные вопросы