RecyclerView导致回收时出现问题 [英] RecyclerView causes issue when recycling
问题描述
我有一个使用RecyclerView
创建的项目的列表.当用户单击其中之一时,我将更改所选项目的背景颜色.
问题是,当我滚动浏览项目时,它们被回收了,其中一些项目获得了所选项目的背景色(这是错误的).
在这里,您可以看到我的Adapter
的代码:
I have a list of items that I created using RecyclerView
. When the user clicks on one of them I change the background color of that selected item.
The problem is, when I scroll through my items, and they get recycled, some of the items get the selected item's background color (which is wrong).
Here you can see my Adapter
's code:
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() {
}
}
推荐答案
问题是recyclerView
回收行为,该行为会将屏幕外的ViewHolder
项目分配给将要在屏幕上显示的新项目.
我不建议您像上述所有答案一样基于ViewHolder
对象绑定逻辑.这真的会引起您的问题.
您应该基于数据对象而不是ViewHolder
对象的状态来构建逻辑,因为您永远都不知道何时回收它.
The problem is recyclerView
recycling behavior which assign your out of screen ViewHolder
items to new items coming to be displayed on screen.
I would not suggest you to bind your logic based on ViewHolder
object as in all above answers. It will really cause you problem.
You should build logic based on the state of your data object not ViewHolder
Object as you will never know when it gets recycled.
假设您保存了一个
ViewHolder
中的状态 boolean isSelected 进行检查,但如果为true,则当回收此viewHolder
时,新项的状态将相同.
Suppose you save a
state boolean isSelected in ViewHolder
to check, but and if it is true, then the same state will be there for new Item when this viewHolder
will be recycled.
更好的方法是在DataModel对象中保留任何状态.在您的情况下,仅选择了布尔值.
Better way to do above is holding the any state in DataModel object. In your case just a boolean 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在评论中问,
As @Gabriel asked in comment,
如果要一次选择一个项目怎么办?
what if one want to select a single item at time?
在那种情况下,同样不要将选定的项目状态保存在ViewHolder
对象中,因为它会被回收并引起问题.更好的方法是在Adapter
类而不是ViewHolder
中有一个字段int selectedItemPosition
.
以下代码段显示了它.
In that case, again one should not save selected item state in ViewHolder
object, as the same it gets recycled and cause you problem. For that better way is have a field int selectedItemPosition
in Adapter
class not ViewHolder
.
Following code snippet show it.
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);
}
}
}
如果只需要维护选定的Item状态,我强烈不建议使用Adapter类的 notifyDataSetChanged()方法,因为RecyclerView在这些情况下提供了更大的灵活性.
If you only have to maintain selected Item state, I strongly discourage use of notifyDataSetChanged() method of Adapter class as RecyclerView provides much more flexibility for these cases.
这篇关于RecyclerView导致回收时出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!