RecyclerView adapter prendre de mauvaises valeurs

j'ai un RecyclerView qui montre deux types de View s l'une représente une publication Utilisateur et l'autre représente une publication D'événement. Les deux ont des éléments en commun, par exemple un TextView cela montre un horodatage. J'ai donc créé un PublicationViewHolder cette TextView pointer l'heure dans une variable et la charger. Mon problème est que l'adaptateur, initialement, charger les bonnes valeurs, mais quand je fais défiler vers le bas, et faire défiler vers le haut à nouveau, les valeurs dans les positions sont changées par des valeurs d'autres positions. Voici le code:

public class PublicationViewHolder extends RecyclerView.ViewHolder {

    private TextView vTimeStamp;

    public PublicationViewHolder(View itemView) {
        super(itemView);
        this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp);
    }

    public void load(Publication publication, int i) {
        load(publication);
        try {
            if (Publication.TYPE_USER_PUBLICATION == publication.getType()) {
                load((UserPublication) publication);
            } else if (Publication.TYPE_EVENT_PUBLICATION == publication.getType()) {
                load((EventPublication) publication);
            }
        } catch (ClassCastException e) {
            throw new RuntimeException("Publication type cast fail. See PublicationViewHolder.");
        }
    }

    public void load(Publication publication) {
        vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }

    public void load( UserPublication publication) {
        //This method is override by UserPublicationViewHolder
    };

    public void load( EventPublication publication) {
        //This method is override by EventPublicationViewHolder
    };

}

Maintenant, je vais faire mon UserPublicationViewHolder pour les utilisateurs publications seulement.

public class UserPublicationViewHolder extends PublicationViewHolder {
    private  ImageView vImageView, vLikeButton, vDislikeButton, vFavButton, vEditPost, vDeletePost;
    private  TextView vText, vUsername, vLikeCount, vDislikeCount, vFavCount;
    private  PostImagesLayout vImagesContainer;
    private TagCloudLocationFriends tagsView;

    public UserPublicationViewHolder(View itemView) {
        super(itemView);
        vImageView = (ImageView) itemView.findViewById(R.id.img_view_publication_user);
        vText = (TextView) itemView.findViewById(R.id.txt_view_publication_text);

        vLikeCount = (TextView) itemView.findViewById(R.id.txt_view_like_count);
        vFavCount = (TextView) itemView.findViewById(R.id.txt_view_fav_count);
        vDislikeCount = (TextView) itemView.findViewById(R.id.txt_view_dislike_count);

        vUsername = (TextView) itemView.findViewById(R.id.txt_view_publication_user_name);
        vLikeButton = (ImageView) itemView.findViewById(R.id.img_view_like);
        vDislikeButton  = (ImageView) itemView.findViewById(R.id.img_view_dislike);
        vFavButton  = (ImageView) itemView.findViewById(R.id.img_view_fav);
        vImagesContainer = (PostImagesLayout) itemView.findViewById(R.id.container_post_images);

        tagsView = (TagCloudLocationFriends) itemView.findViewById(R.id.location_friends_tag);

        // edit - remove icons
        vDeletePost = (ImageView) itemView.findViewById(R.id.img_view_delete_post);
        vEditPost = (ImageView) itemView.findViewById(R.id.img_view_edit_post);
    }


    @Override
    public void load(UserPublication publication) {
        //Load the UserPublicationViewHolder specific views.
    }
}

maintenant je vais faire la même chose mais pour les publications de L'événement

public class EventPublicationViewHolder extends PublicationViewHolder {

    private TextView vTextViewTitle;
    private TextView vTextViewText;

    public EventPublicationViewHolder(View itemView) {
        super(itemView);
        vTextViewTitle = (TextView) itemView.findViewById(R.id.txt_view_publication_event_title);
        vTextViewText = (TextView) itemView.findViewById(R.id.txt_view_publication_event_text);
    }

    @Override
    public void load(EventPublication publication) {
        //Load the EventPublicationViewHolder specifics views
    }
}

maintenant voici mon adaptateur RecyclerView:

public class PublicationAdapter extends RecyclerView.Adapter<PublicationViewHolder> {

    public static final int USER_PUBLICATION_TYPE = 1;
    public static final int EVENT_PUBLICATION_TYPE = 2;
    private List<Publication> publications = new ArrayList<Publication>();

    public List<Publication> getPublications() {
        return publications;
    }

    public void setPublications(List<Publication> publications) {
        this.publications = publications;
    }

    @Override
    public int getItemViewType(int position) {
        if (publications.get(position) instanceof UserPublication) {
            return USER_PUBLICATION_TYPE;
        }
        if (publications.get(position) instanceof EventPublication) {
            return EVENT_PUBLICATION_TYPE;
        }
        throw new RuntimeException("Unknown view type in PublicationAdapter");
    }

    @Override
    public PublicationViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
        View v;
        switch (type) {
            case USER_PUBLICATION_TYPE:
                v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_user_publication, viewGroup, false);
                return new UserPublicationViewHolder(v);
            case EVENT_PUBLICATION_TYPE:
                v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_event_publication, viewGroup, false);
                return new EventPublicationViewHolder(v);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(PublicationViewHolder aPublicationHolder, int i) {
        aPublicationHolder.load(publications.get(i), i);
    }

    @Override
    public long getItemId(int position) {
        //Here I tried returning only position or 0 without luck.
        //The id is unique BTW
        return publications.get(position).getId();
    }

    @Override
    public int getItemCount() {
        return publications.size();
    }

}

Je ne sais pas ce qui peut être erroné, la Publicationutilisatrice et la Publicationévénements s'étendent toutes deux de la Publication. Je ne fais pas de demande ou ne recharge pas l'adaptateur. Je ne charge que l'adaptateur lorsque.

mise à Jour:

BTW j'utilise ce RecyclerView à l'intérieur d'un Fragment qui est chargé dans un PageAdapter qui est chargé dans un ViewPager qui est à l'intérieur d'un Fragment, peut-être que c'est le problème?

mise à Jour: C'est l'autre code contraignant.

C'est la méthode de charge du UserPublicationViewHolder.

    @Override
    public void load(UserPublication publication) {
        PicassoHelper.publicationUser(getActivity(), publication.getUser().getAvatarUrl(),
                vImageView);
        vText.setText(publication.getText());
        vUsername.setText(publication.getUser().getName());
        boolean hasLocation = false;
        if (publication.getImages().length > 0) {
            vImagesContainer.setImages(publication.getImages());
        } else {
            vImagesContainer.setVisibility(View.GONE);
        }
        tagsView.setTags(new ArrayList<MinikastTag>());
        tagsView.drawTags();

        if(publication.getLocation() != null || publication.getTaggedFriends().size() > 0){
            if(publication.getLocation() != null){
                hasLocation = true;
                tagsView.add(new MinikastTag(1,"Post from ",1));
                tagsView.add(new MinikastTag(2, publication.getLocation().getName(), 2));
            }
            if(publication.getTaggedFriends().size() > 0){
                if(hasLocation)
                    tagsView.add(new MinikastTag(3," with ",1));
                else
                    tagsView.add(new MinikastTag(3,"With ",1));

                int i = 0;
                for(User aUser: publication.getTaggedFriends()){
                    MinikastTag aTag;
                    if(i == publication.getTaggedFriends().size() - 1 ) {
                        aTag = new MinikastTag(4, aUser.getName(), 3);
                        aTag.setUserID(aUser.getId());
                        aTag.setUserName(aUser.getName());
                        tagsView.add(aTag);
                    } else {
                        aTag = new MinikastTag(4, aUser.getName() + ", ", 3);
                        aTag.setUserID(aUser.getId());
                        aTag.setUserName(aUser.getName());
                        tagsView.add(aTag);
                    }
                    i = i+1;
                }
            }
        }
        tagsView.drawTags();

        // likes, dislikes, favs
        if(publication.getLikesAmount() > 0)
            vLikeCount.setText(String.valueOf(publication.getLikesAmount()));

        if(publication.getDislikesAmount() > 0)
            vDislikeCount.setText(String.valueOf(publication.getDislikesAmount()));

        if(publication.getLovesAmount() > 0)
            vFavCount.setText(String.valueOf(publication.getLovesAmount()));

        // reset buttons
        vFavButton.setPressed(false);
        vDislikeButton.setPressed(false);
        vLikeButton.setPressed(false);

        if(publication.getRelationship().equals("LOVE"))
            vFavButton.setPressed(true);
        else if (publication.getRelationship().equals("LIKE"))
            vLikeButton.setPressed(true);
        else if (publication.getRelationship().equals("DISLIKE"))
            vDislikeButton.setPressed(true);

        // edit - remove icons

        if(String.valueOf(publication.getUser().getId()).equals(StartupSharedPreferences.getProfileId())){
            vEditPost.setVisibility(View.VISIBLE);
            vDeletePost.setVisibility(View.VISIBLE);
        }else{
            vEditPost.setVisibility(View.INVISIBLE);
            vDeletePost.setVisibility(View.INVISIBLE);
        }
    }
}

Et c'est la méthode de chargement de la EventPublicationViewHolder:

@Override
public void load(EventPublication publication) {
    vTimeStamp.setVisibility(View.GONE);
    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //GoTo.eventDetail(getActivity(), publication);
        }
    });
    vTextViewTitle.setText(publication.getTitle());
    vTextViewText.setText(publication.getText());
}

je j'ai commenté un code juste parce que j'étais en train de le tester, mais comme vous pouvez le voir, Je ne fais que des setTexts et assing quelques images.

et c'est comme ça que je règle l'adaptateur, le Gestionnaire de ligne, etc. Dans la méthode onViewCreated du fragment.

vRecyclerView = (FixedRecyclerView) view.findViewById(R.id.recycler_view_publications);
        vSwipeRefresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container);
        mFeedCallback.onScrollReady(vRecyclerView);
        mLayoutManager = buildLayoutManager();
        vRecyclerView.setLayoutManager(mLayoutManager);
        vRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
        mAdapter = new PublicationAdapter();
        vSwipeRefresh.setOnRefreshListener(this);
        vSwipeRefresh.setColorSchemeResources(R.color._SWIPER_COLOR_1, R.color._SWIPER_COLOR_2,
                R.color._SWIPER_COLOR_3, R.color._SWIPER_COLOR_4);
        vRecyclerView.setAdapter(mAdapter);

BTW L'adaptateur est chargé avec l'ensemble de données dans une méthode personnalisée que j'ai, appelé onHttpClientReady, mais cela ne semble pas être le problème.

voici quelques screenshots:

en haut de la liste quand j'entre dans l'application pour la première fois:

enter image description here

Puis, quand je reviens: enter image description here

BTW les boutons like, dislike et favorite, si quelqu'un les a cliqués plus d'une fois, afficheront une valeur numérique, ces valeurs sont aussi égarées si elles le sont.

mise à jour: Maintenant je sais que ce n'était pas à cause des fragments imbriqués. J'ai changé mon code de la façon dont, maintenant, chaque fragment d'onglet est dans le PageStateAdapter qui est à l'intérieur du ViewPager qui est à l'intérieur d'une activité. Mais le problème est toujours là.

mise à jour: J'ai trouvé que la méthode getItemId n'est jamais exécutée, IDK pourquoi encore.

20
demandé sur Rohit Suthar 2015-01-19 22:10:49

5 réponses

je suggère de revoir la hiérarchie et l'usage de votre classe. En général, si vous faites un type == type type d'opération dans une classe de base, alors vous êtes vaincre le but de l'abstraction et de l'héritage. Quelque chose comme ça marcherait pour vous:

public abstract class PublicationViewHolder extends RecyclerView.ViewHolder {
    private TextView mTimeStamp;

    public PublicationViewHolder(View itemView) {
        mTimeStamp = (TextView)itemView.findViewById(R.id. txt_view_publication_timestamp);
    }

    public void bindViews(Publication publication) {
        mTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }
}

maintenant votre "event" ou "user publications" dérivent simplement de cette classe et implémentent le constructeur et bindViews() méthode. assurez-vous d'appeler la superclasse dans les deux cas. également, assurez-vous que vous définissez tous les voir la mise en page pour la publication spécifique dans votre bindViews() méthodes.

dans votre adaptateur, vous avez juste besoin de créer le support correct basé sur le type de publication à cette position dans votre ensemble de données:

public class PublicationAdapter extends RecyclerView.Adapter {
    private ArrayList<Publication> mPubs;

    //  Your other code here, like
    //  swapPublications(), getItemCount(), etc.
    ...

    public int getItemViewType(int position) {
        return mPubs.get(position).getType();
    }

    public PublicationViewHolder createViewHolder(ViewGroup parent, int type) {
        PublicationViewHolder ret;
        View root;
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        if (type == USER_PUBLICATION_TYPE) {
            root =
                inflater.inflate(R.layout.view_holder_user_publication,
                    parent,
                    false);

            ret = new UserPubHolder(root);
        } else {
            root =
                inflater.inflate(R.layout.view_holder_event_publication,
                    parent,
                    false);

            ret = new EventPubHolder(root);
        }

        return ret;
    }

    public bindViewHolder(PublicationViewHolder holder, int position) {
        holder.bindViews(mPubs.get(position));
    }
}
3
répondu Larry Schiefer 2015-01-19 20:52:11

cela se produit habituellement quand vous avez quelque chose comme "si (field != null) titulaire.setField(sur le terrain)", sans d'autre. Le support est recyclé, cela signifie qu'il y aura des valeurs, donc vous devez nettoyer ou remplacer chaque valeur, si elle est nulle, vous devriez nullit, si elle n'est pas, vous devriez l'écrire, toujours. Il est tard, mais c'est une réponse pour les autres.

64
répondu Ivan 2015-05-18 11:06:38

pour moi, la configuration de setHasStableIds(false) résolu le problème.

5
répondu Shaegorath 2016-09-05 10:55:59

Eu le même Problème avec async images chargées, qui avait différentes hauteurs. Donc avec debugger, vous pouvez voir que les positions pour le recyclage dépendent de la taille réelle des vues.

la solution Simple pour moi était de spécifier différentes tailles, de sorte que le système connaît la taille exacte de tous les articles. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

par exemple paysage, portrait et carré.

alors j'ai créé des vues séparées et je les ai utilisées comme: (simplifié)

public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
  // ...
  public static class ViewHolderLandscape extends RecyclerView.ViewHolder { ... }
  public static class ViewHolderPortrait  extends RecyclerView.ViewHolder { ... }
  public static class ViewHolderSquare    extends RecyclerView.ViewHolder { ... }

  @Override
  public int getItemViewType(int position) {      
    return mDataset.get(position).getImageType();
  }

  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    int mLayoutId = 0;

    switch (viewType) {
        case 0:
            mLayoutId = R.layout.list_item_landscape;
            break;
        case 1:
            mLayoutId = R.layout.list_item_portrait;
            break;
        case 2:
            mLayoutId = R.layout.list_item_square;
            break;
    }

    View v = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);        
    ButterKnife.inject(this, v);

    return new ViewHolder(v);
  }
}

finalement RecycleView ne se confond pas sur les différentes tailles d'articles dynamiques.

1
répondu everyman 2015-06-24 13:06:45

la seule grande variable dans votre code de liaison est dans votre formatage de date: DateFormatter.getTimeAgo(publication.getTimeStamp())

sans voir cette classe directement, il est difficile de dire avec certitude, mais il semble que, si l'horodatage est immuable, mais que le formatteur est basé sur l'heure actuelle, alors ce serait cohérent avec le changement de texte lorsque la vue est en rebond.

je pense à un gros problème (et un peu d'un côté) est la lisibilité du code, ce qui rend difficile de repérer facilement le problème visuellement. Le modèle d'héritage et les surcharges ici rendent difficile de raisonner au sujet du code et de décider quel chemin est pris et si elle fait la bonne chose. Voici un peu de code de serviettes (Je ne l'ai pas construit ou exécuté) en utilisant une approche plus compositionnelle qui pourrait être une organisation plus claire et le rendre plus facile à déboguer les problèmes:

Nouvelle classe d'assistance pour la commune de la vue titulaire code, remplace PublicationViewHolder:

public class PublicationViewHolderHelper {
    private final TextView vTimeStamp;

    public PublicationViewHolder(View itemView) {
        super(itemView);
        this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp);
    }

    /** Binds view data common to publication types. */
    public void load(Publication publication) {
        vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }
}

EventPublicationViewHolder comme un exemple (faire la même chose pour UserPublicationViewHolder):

public class EventPublicationViewHolder extends ViewHolder {
    private final PublicationViewHolderHelper helper;

    // View fields...

    public EventPublicationViewHolder(View itemView) {
         super(itemView);
         helper = new PublicationViewHolderHelper(itemView);
         // Populated view fields...
    }

    @Override
    public void load(EventPublication publication) {
        helper.load(publication);
        //Load the EventPublicationViewHolder specifics views
    }
}

notez qu'il n'y a pas de classe de base dans votre adaptateur, et aussi pas besoin de vérification de type, donc il y a beaucoup moins de code.

maintenant l'adaptateur reste le même à l'exception du type générique et onBindViewHolder:

public class PublicationAdapter extends RecyclerView.Adapter<ViewHolder> {
    ...
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        final Publication publication = publications.get(position);
        final int viewType = getItemViewType(position);
        switch (viewType) {
            case USER_PUBLICATION_TYPE:
                ((UserPublicationViewHolder) viewHolder).load((UserPublication) publication);
                break;
            case EVENT_PUBLICATION_TYPE:
                ((EventPublicationViewHolder) viewHolder).load((EventPublication) publication);
                break;
            default:
                // Blow up in whatever way you choose.
        }
    }
    ...
}

remarquez qu'il maintient un modèle très similaire à votre onCreateViewHolder, donc il y a non seulement moins de code global mais aussi plus de cohérence interne. Ce n'est certainement pas la seule façon de le faire, juste une suggestion basée sur votre cas d'utilisation particulier.

0
répondu lopar 2015-01-19 20:14:11