【问题标题】:RecyclerView adapter taking wrong valuesRecyclerView 适配器采用错误的值
【发布时间】:2015-01-19 19:10:49
【问题描述】:

我有一个RecyclerView,它显示了两种Views,一种代表用户发布,另一种代表事件发布。两者都有共同的元素,例如显示时间戳的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 扩展而来的。我没有做一些请求或重新加载适配器。我只加载一次适配器。

更新:

顺便说一句,我在 Fragment 中使用此 RecyclerView,该 RecyclerView 加载在 PageAdapter 中,该 PageAdapter 加载在 Fragment 内的 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 和 assing 一些图像。

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

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

顺便说一句,适配器是在我拥有的自定义方法中加载数据集的,称为 onHttpClientReady,但这似乎不是问题。

以下是一些截图:

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

然后当我回来时:

顺便说一句,喜欢,不喜欢和喜欢的按钮,如果有人多次点击它们,将显示一个数值,如果是,这些值也会错位。

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

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

【问题讨论】:

  • 检查public void load(Publication publication, int i) - 你从不使用i
  • 我不这么认为。确实,int i 参数没用,我的错。但发布实例实际上是正确的,正如您在 onBindViewHolder 方法中看到的那样。我更新了我的答案,我认为问题来自那方面。我什至尝试将所有这些参数都设置为最终参数,但没有运气。
  • 如果用户点击喜欢得到更新的喜欢计数的响应,而设置视图到该位置未在正确的视图 android 上更新
  • getItemId 中的getIds 方法是什么??如何获得。

标签: android android-recyclerview


【解决方案1】:

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

【讨论】:

  • 谢谢!你让我很开心!)
  • 这绝对应该是公认的答案。我什至不知道这甚至是一件事,现在我查看我的项目,这解释了许多我无法轻易复制的小错误。感谢伊万的提示
  • 就是这样。基本上我确实有一个 else 块。我有两种类型的视图,它们都共享相同的布局和元素。 if 块隐藏了一个默认设置为visible 的元素。 else 块只是填充了我已经显示的元素。根据这个答案,我应该在 else 块中再次将元素设置为 visible,尽管它在 XML 中已经是 visible。它工作正常,一切正常,即使在被回收之后也是如此。
  • 谢谢这真的解决了我的问题
  • @Ivan 我在这里可能有类似的问题:stackoverflow.com/questions/43531900/… 我将不胜感激有关如何解决的任何想法和想法。
【解决方案2】:

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

【讨论】:

    【解决方案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() 方法。 请务必在这两种情况下都调用超类。另外,请务必在布局中为特定出版物设置 every 视图你的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));
        }
    }
    

    【讨论】:

    • 谢谢,我知道我的代码很乱,你提出的重构非常好,但主要问题是,由于某种原因,当我使用 RecyclerView 向下滚动时,再往上走,每个 RecyclerView 的 item 中显示的数据会从另一个位置的另一个 item 中更改为一些其他数据。我看不出这个重构如何帮助我解决我的问题。正如我在更新中所说,我认为这个问题是因为嵌套片段。你同意吗?
    • 不,不是嵌套片段。这很可能是绑定的完成方式。通常,当您看到这种情况时,这是因为视图正在被回收,并且绑定操作并未将层次结构中的所有视图设置为与数据匹配的状态。
    • 从小处着手重构:按照我上面概述的模式只做“用户发布”。一旦你做对了,你就可以添加其他发布类型并验证它是否运行良好。
    【解决方案4】:

    在异步加载图像时遇到了同样的问题,图像有不同的高度。所以使用调试器你可以看到,回收的位置取决于视图的实际大小。

    对我来说,简单的解决方案是指定不同的尺寸,以便系统知道所有物品的确切尺寸。 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 不会对不同/动态的项目大小感到困惑。

    【讨论】:

      【解决方案5】:

      绑定代码中的一个大变量是日期格式: 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 保持着非常相似的模式,因此不仅整体代码更少,而且内部一致性也更高。这当然不是唯一的方法,只是根据您的特定用例提出的建议。

      【讨论】:

      • 不错的重构,但时间戳不是我要绑定的唯一数据。在UserPublicationViewHolderload 方法中,我绑定了发布的喜欢数量、不喜欢、收藏等。当我使用RecyclerView 向下滚动时,所有这些字段、所有这些数据都被完全弄乱了。你认为这可能是因为嵌套的片段吗?当我添加片段时,所有数据都很好,一切看起来都很好,然后,当我向下滚动并回到 Recycler 的顶部时,所有数据都乱了。
      • 看看其他绑定代码会很有帮助。从最初的问题来看,似乎只设置了一个文本视图。这可能会提供一些提示。另外,您可以发布您用于在 RV 上设置布局管理器和适配器的 sn-p 吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-07
      • 1970-01-01
      相关资源
      最近更新 更多