web-dev-qa-db-fra.com

RecyclerView onBindViewHolder appelé uniquement lorsque getItemViewType change

J'ai un viewholder avec plusieurs types de vues.

Lors du défilement, onBindViewHolder n'est appelé que lorsque getItemViewType change de valeur. Cela entraîne que mes éléments de liste ne sont pas mis à jour correctement.

Est-ce un bug? Ou je fais quelque chose de mal ici. Cela semble un comportement très étrange de la nouvelle classe recyclerView.

Voici mon adaptateur:

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

J'ai également résumé les éléments comme ceci:

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

Et pour les 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);
    }
}

Cela fonctionne bien pour les premières lignes visibles, mais il échoue.

26
Richard

J'ai oublié d'implémenter getItemId lors de l'utilisation de setHasStableIds(true);

Une mise en œuvre qui a résolu le problème!

39
Richard

Lorsque vous définissez setHasStableIds(true) signifie que si nous demandons un ID pour un emplacement, tout élément spécifique renverra toujours le même ID, quelle que soit sa position dans la liste. Cela nous permet d'identifier un élément spécifique même si sa position change, ce qui sera utile plus tard.

Pour cela, vous devez implémenter la méthode getItemId dans l'adaptateur et renvoyer simplement ses positions.

@Override
    public long getItemId(int position) {
        return position;
    }
7
Umesh