I have a viewholder with multiple viewtypes.
When scrolling onBindViewHolder
is only called when getItemViewType
changes value. This causes my list items to not be updated properly.
Is this a bug? Or I'm i doing something wrong here. This seems very strange behaviour from the new recyclerView
class.
Here is my adapter:
package se.davison.smartrecycleradapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.ViewGroup; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; /** * Created by richard on 10/11/14. */ public class SectionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final String TAG = SectionAdapter.class.getSimpleName(); private final LayoutInflater inflater; private final int sectionLayoutId; private SparseIntArray positionSection; private LinkedHashMap<AdapterItem, List<AdapterItem>> items = new LinkedHashMap<AdapterItem, List<AdapterItem>>(); private List<Class<? extends AdapterItem>> itemTypes = new ArrayList<Class<? extends AdapterItem>>(20); public SectionAdapter(Context context, int sectionLayoutId, LinkedHashMap<AdapterItem, List<AdapterItem>> items) { this.inflater = LayoutInflater.from(context); this.sectionLayoutId = sectionLayoutId; this.items = items; initList(items); } public SectionAdapter(Context context, int sectionLayoutId) { this.inflater = LayoutInflater.from(context); this.sectionLayoutId = sectionLayoutId; this.items = new LinkedHashMap<AdapterItem, List<AdapterItem>>(); initList(items); } @Override public int getItemViewType(int position) { AdapterItem item = getItem(position); Log.d(TAG, position + ": class " + item.getClass()); return itemTypes.indexOf(item.getClass()); } @Override public int getItemCount() { int count = 0; if (items == null) { return 0; } for (AdapterItem key : items.keySet()) { count++; List<AdapterItem> childItems = items.get(key); if (childItems != null) { count += childItems.size(); } } return count; } private void initList(HashMap<AdapterItem, List<AdapterItem>> items) { positionSection = new SparseIntArray(items.size()); int count = 0; int sectionIndex = -1; for (AdapterItem key : items.keySet()) { Class headerClass = key.getClass(); if (!itemTypes.contains(headerClass)) { itemTypes.add(headerClass); } List<AdapterItem> childItems = items.get(key); sectionIndex = count; if (childItems != null) { for (AdapterItem item : childItems) { Class clazz = item.getClass(); if (!itemTypes.contains(clazz)) { itemTypes.add(clazz); } positionSection.put(count, sectionIndex); count++; } } count++; } setHasStableIds(true); } private AdapterItem getItem(int position) { int totalChildCount = 0; int separatorCount = 0; for (AdapterItem key : items.keySet()) { if (position == 0 || position == totalChildCount + separatorCount) { return key; } separatorCount++; List<AdapterItem> list = items.get(key); int couldCount = countList(list); if (position < totalChildCount + separatorCount + couldCount) { return list.get(position - (totalChildCount + separatorCount)); } totalChildCount += couldCount; } return null; } public void setItems(LinkedHashMap<AdapterItem, List<AdapterItem>> items) { this.items = items; notifyDataSetChanged(); } public void setItemsAtHeader(int id, List<AdapterItem> items) { AdapterItem header = null; for (AdapterItem key : this.items.keySet()) { if (key.headerId() == id) { header = key; break; } } if (header == null) { throw new IllegalArgumentException(String.format("No header with id %s is found", id)); } setItemsAtHeader(header, items); } private void setItemsAtHeader(AdapterItem header, List<AdapterItem> items) { this.items.put(header, items); } private int countList(List<?> list) { return list == null ? 0 : list.size(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { AdapterItem firstItem = getFirstItemWithClass(itemTypes.get(viewType)); return firstItem.onCreateViewHolder(inflater, viewGroup); } private AdapterItem getFirstItemWithClass(Class<? extends AdapterItem> clazz) { for (AdapterItem key : items.keySet()) { if (key.getClass() == clazz) { return key; } List<AdapterItem> childItems = items.get(key); if (childItems != null) { for (AdapterItem item : childItems) { if (item.getClass() == clazz) { return item; } } } } throw new IllegalStateException("Something is wrong, you dont have any items with that class in your list"); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { AdapterItem item = getItem(position); Log.d(TAG, "ITEM = " + item.getClass().getSimpleName()); Log.d(TAG, "POS = " + position); if (item instanceof OneLineAdapterItem) { Log.d(TAG, "TEXT = " + ((OneLineAdapterItem) item).getText()); } item.onBindViewHolder(viewHolder, position); } }
I've also abstracted out the items like so:
public abstract class AdapterItem<VH extends RecyclerView.ViewHolder> { public boolean isHeader(){ return false; } public int headerId(){ return -1; } public abstract VH onCreateViewHolder(LayoutInflater inflater, ViewGroup parent); public abstract void onBindViewHolder(VH viewHolder, int position); }
And for sections
public class SectionAdapterItem extends AdapterItem<SectionAdapterItem.ViewHolder> { private String text; private boolean dividerVisible = false; public static class ViewHolder extends RecyclerView.ViewHolder{ TextView titel; ImageView divider; public ViewHolder(View itemView) { super(itemView); titel = (TextView) itemView.findViewById(android.R.id.title); divider = (ImageView) itemView.findViewById(android.R.id.icon); } } public void setDividerVisible(boolean dividerVisible) { this.dividerVisible = dividerVisible; } public boolean isDividerVisible() { return dividerVisible; } @Override public boolean isHeader() { return true; } @Override public int headerId() { return super.headerId(); } public SectionAdapterItem(String text) { this.text = text; } @Override public ViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent) { return new ViewHolder(inflater.inflate(R.layout.top_header, parent, false)); } @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.titel.setText(text); viewHolder.divider.setVisibility(dividerVisible?View.VISIBLE:View.GONE); } }
It works fine for the frist visible rows, but then it fails.