RecyclerView适配器采用错误的值

时间:2015-01-19 19:10:49

标签: android android-recyclerview recycler-adapter

我有RecyclerView显示两种View s代表用户出版物,另一种代表事件出版物。两者都有共同的元素,例如显示时间戳的TextView。所以我创建了一个PublicationViewHolder,它将此TextView时间戳记加入变量并加载它。我的问题是适配器最初加载正确的值,但是当我向下滚动并再次向上滚动时,位置中的值会被其他位置的值更改。这是代码:

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

}

现在我只为用户发布我的UserPublicationViewHolder

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.
    }
}

现在我将对事件出版物

做同样的事情
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
    }
}

现在这是我的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();
    }

}

我不知道哪些是错的,UserPublication和EventPublication都来自Publication。我没有做一些请求或重新加载适配器。我只加载适配器一次。

更新

BTW我在片段中使用这个RecyclerView,它装载在PageAdapter中,它装载在片段内的ViewPager中,这可能是个问题吗?

更新 这是另一个绑定代码。

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

这是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());
}

我评论了一些代码只是因为我正在测试,但正如你所看到的,我只做了setTexts并且给出了一些图像。

这就是我设置适配器,LinearLayoutManager等的方法。在片段的onViewCreated方法中。

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适配器在我自己的自定义方法中加载了数据集,名为onHttpClientReady,但这似乎不是问题。

以下是一些截图:

我第一次进入应用程序时列表的顶部:

enter image description here

然后当我回来时: enter image description here

BTW之类的,不喜欢和喜欢的按钮,如果有人多次点击它们,会显示一个数值,如果它们也是错位的。

更新 现在我知道那不是因为嵌套的片段。我改变了我的代码,现在,每个选项卡片段都在PageStateAdapter中,该PageStateAdapter位于Activity内部的ViewPager中。但问题仍然存在。

更新 我发现getItemId方法永远不会被执行,IDK为什么还要执行。

5 个答案:

答案 0 :(得分:66)

当你有“if(field!= null)holder.setField(field)”之类的东西时,通常会发生这种情况。持有者被回收,这意味着它将具有值,所以你需要清理或替换每个值,如果它是null你应该nullit,如果不是,你应该写它,总是。现在已经晚了,但作为其他人的答案。

答案 1 :(得分:6)

对我来说设置setHasStableIds(false)解决了这个问题。

答案 2 :(得分:3)

我建议您查看类的层次结构和用法。通常,如果您在基类中执行type == type类操作,那么您正在破坏抽象和继承的目的。这样的事情对你有用:

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

现在,您的“事件”或“用户出版物”只是派生自此类并实现构造函数和bindViews()方法。 请务必在两种情况下都调用超类。 此外,请确保在布局中为特定出版物设置每个视图你的bindViews()方法。

在适配器中,您只需根据数据集中该位置的发布类型创建正确的持有者:

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 :(得分:1)

与异步加载的图像有相同的问题,不同的高度。因此,使用调试器,您可以看到,回收的位置取决于视图的实际大小。

对我来说,简单的解决方案是指定不同的大小,因此系统知道所有项目的确切大小。 https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

例如风景,肖像和广场。

所以我创建了单独的视图并使用它们:(简化)

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

最后,RecycleView不会对不同/动态的项目大小感到困惑。

答案 4 :(得分:0)

绑定代码中的一个大变量是日期格式: DateFormatter.getTimeAgo(publication.getTimeStamp())

如果不直接看到那个类,很难肯定地说,但似乎,如果时间戳是不可变的,但格式化程序是基于当前时间的,那么这将与文本更改时一致这种观点是反弹的。

我认为一个更大的问题(有点暂时)是代码的可读性,这使得很难直观地发现问题。这里的继承模式和重载使得很难推断代码并决定采用哪条路径以及它是否做了正确的事情。这里有一些餐巾代码(没有构建它或运行它),使用更加组合的方法,可能是一个更清晰的组织,并使调试问题更容易:

公共视图持有者代码的新助手类替换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作为示例(对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
    }
}

请注意,您的适配器中现在没有基类,也不需要进行类型检查,因此代码更少。

现在适配器保持不变,除了泛型类型和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.
        }
    }
    ...
}

请注意,它与onCreateViewHolder保持了非常相似的模式,因此不仅总体代码更少,而且内部一致性也更高。这当然不是唯一的方法,只是基于您的特定用例的建议。