RecyclerView onBindViewHolder solo se llama cuando cambia getItemViewType


Tengo un viewholder con varios tipos de vista.

Cuando se desplaza onBindViewHolder solo se llama cuando getItemViewType cambia de valor. Esto hace que los elementos de mi lista no se actualicen correctamente.

Es esto un error? O estoy haciendo algo mal aquí. Esto parece un comportamiento muy extraño de la nueva clase recyclerView.

Aquí está mi adaptador:

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

También he resumido los elementos de esta manera:

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

Y para las secciones

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

Funciona bien para las primeras filas visibles, pero entonces falla.

Author: Richard, 2014-11-13

2 answers

He olvidado implementar getItemId al usar setHasStableIds(true);

La implementación de que resolvió el problema!

 37
Author: Richard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-29 10:54:34

Cuando se establece setHasStableIds(true) significa que si solicitamos un ID para una ubicación, cualquier elemento específico siempre devolverá el mismo ID independientemente de su posición dentro de la lista. Esto nos permite identificar un elemento específico incluso si su posición cambia, lo que será útil más adelante.

Para eso necesita implementar el método getItemId en el adaptador y simplemente devolver sus posiciones.

@Override
    public long getItemId(int position) {
        return position;
    }
 7
Author: Umesh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-14 06:27:16