RecyclerView adapter taking wrong values

蹲街弑〆低调 提交于 2019-11-28 23:08:09

I'd suggest reviewing your class hierarchy and usage. In general, if you are doing a type == type kind of operation in a base class then you are defeating the purpose of the abstraction and inheritance. Something like this would work for you:

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

Now your "event" or "user publications" simply derive from this class and implement the constructor and bindViews() method. Be sure to call through to the superclass in both cases. Also, be sure that you set every view in the layout for the specific publication in your bindViews() methods.

In your adapter, you just need to create the correct holder based on the publication type at that position in your data set:

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

This usually happens when you have something like "if (field != null) holder.setField(field)", without an else. The holder is recycled, this means that it will have values there, so you need to clean or replace EVERY value, if it's null you should nullit, if it's not, you should write it, ALWAYS. It's late, but, as an answer for others.

Fire in the Hole

for me setting setHasStableIds(false) solved the problem.

Had same Problem with async loaded images, which had different heights. So with debugger you could see, that positions for recycling depends on the actual size of views.

Simple solution for me was to specify different sizes, so system knows the exact size of all items. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

For example landscape, portrait and square.

So I created separate views and used them like: (simplified)

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

Finally RecycleView doesn't get confused about different/dynamic item sizes.

The one big variable in your binding code is in your date formatting: DateFormatter.getTimeAgo(publication.getTimeStamp())

Without seeing that class directly it's hard to say for sure, but it seems like, if the timestamp is immutable, but the formatter is based on the current time, then that would be consistent with the text changing when the view is rebound.

I think a bigger issue (and somewhat of an aside) is the readability of the code, which makes it hard to easily spot the problem visually. The inheritance pattern and overloads here make it hard to reason about the code and decide which path is being taken and if it's doing the right thing. Here's some napkin code (haven't built it or run it) using a more compositional approach that might be a clearer organization and make it easier to debug issues:

New helper class for common view holder code, replaces 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 as an example (do the same thing for 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
    }
}

Notice there's no base class now in your adapter, and also no need for type checking, so there's a lot less code.

Now the adapter stays the same with the exception of the generic type and 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.
        }
    }
    ...
}

Notice it maintains a very similar pattern to your onCreateViewHolder, so there's not only less overall code but also more internal consistency. This certainly isn't the only way to do it, just a suggestion based on your particular use case.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!