Recycler视图在多个视图持有者的片段中显示错误的项目

时间:2017-09-18 18:27:14

标签: android android-fragments android-recyclerview recycler-adapter recycle

我的活动包含具有动态创建的片段的视图寻呼机,并且在片段内部有一个带有多个显示数据的视图持有者的回收器视图。第一次显示的数据正确显示,但是当我向下滚动显示错误位置的数据时。我的项目是使用Glide下载和显示的文本和图像,因此我将展示一些适配器主代码,因为它很长

onCreateViewHolder方法

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        // the current view holder
        RecyclerView.ViewHolder viewHolder;

        switch (viewType) {
            case TYPE_HEADER:
                View headerView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_article_header, parent, false);
                viewHolder = new HeaderViewHolder(headerView);
                break;
            case TYPE_EXTRA_INFORMATION:
                View extraInformationViewView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_article_extra_information, parent, false);
                viewHolder = new ExtraArticleInformationViewHolder(extraInformationViewView);
                break;
            case TYPE_TEXT:
                View textView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_content_text, parent, false);
                viewHolder = new TextViewHolder(textView);
                break;
            case TYPE_IMAGE:
                View imageView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_content_image, parent, false);
                viewHolder = new ImageViewHolder(imageView);
                break;
            case TYPE_TITLE:
                View titleView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_content_title, parent, false);
                viewHolder = new TitleViewHolder(titleView);
                break;
            default:
                View emptyView = LayoutInflater.from(parent.getContext()).
                        inflate(R.layout.item_article_empty_view, parent, false);
                viewHolder = new EmptyViewHolder(emptyView);
        }
        return viewHolder;
    }

onBindViewHolder方法及其绑定数据的init方法

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        String type = "";
        if (position - 1 > 0 && position < article.getContent().size()) {
            type = article.getContent().get(position - 1).getType();
        }
    switch (holder.getItemViewType()) {
        case TYPE_HEADER:
            initHeaderBindViewHolder((HeaderViewHolder) holder);
            break;
        case TYPE_EXTRA_INFORMATION:
            initExtraInformationBindViewHolder((ExtraArticleInformationViewHolder) holder);
            break;
        case TYPE_TEXT:
            initTextViewHolder((TextViewHolder) holder, position);
            break;
        case TYPE_IMAGE:
            initImageViewHolder((ImageViewHolder) holder, position);
            break;
        case TYPE_TITLE:
            initTitleViewHolder((TitleViewHolder) holder, position);
            break;
        case TYPE_VIDEO:
            initVideoViewHolder((VideoViewHolder) holder, position);
            break;
        default:
            initEmptyViewHolder((EmptyViewHolder) holder, position);
    }
    }

getItemViewType方法和if a utils方法中的方法来确定正确的视图持有者

 @Override
    public int getItemViewType(int position) {
        if (isArticleItemHeader(position)) {
            return TYPE_HEADER;
        } else if (isArticleItemExtraInformation(position, article)) {
            return TYPE_EXTRA_INFORMATION;
        } else if (isArticleItemText(position, article)) {
            return TYPE_TEXT;
        } else if (isArticleItemImage(position, article)) {
            return TYPE_IMAGE;
        } else if (isArticleItemTitle(position, article)) {
            return TYPE_TITLE;
        } else {
            return TYPE_EMPTY;
        }
    }

图像视图保持器初始化方法

  private void initImageViewHolder(ImageViewHolder holder) {
        //Check separator
        List<Content> contents = article.getContent();
        boolean isUpSeparator = false;
        boolean isDownSeparator = false;
        if (holder.getAdapterPosition() != 1 && holder.getAdapterPosition() != contents.size()) {
            //Check up separator
            if (holder.getAdapterPosition() - 2 > 0) {
                if (contents.get(holder.getAdapterPosition() - 2).getType().equals(CONTENT_SEPARATOR)) {
                    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.contentContainer.getLayoutParams();
                    params.topMargin = (int) dpToPx(context, 4);
                    isUpSeparator = true;
                }
                //Check down separator
            } else if (holder.getAdapterPosition() < contents.size()) {
                if (contents.get(holder.getAdapterPosition()).getType().equals(CONTENT_SEPARATOR)) {
                    isDownSeparator = true;
                }
            }
        }

        Content content = article.getContent().get(holder.getAdapterPosition() - 1);
        if (content != null) {
            if (content.getType().equals(CONTENT_IMAGE)) {
                if (content.getImage() != null) {
                    if (!content.getImage().trim().isEmpty()) {
                        boolean isImageSmallThanScreen = false;

                        //determine if an image is gid or not to change the placeholder
                        if (content.getImage().trim().contains(".gif")) {
                            holder.placeHolderImage.setImageResource(R.drawable.gif_place_holder);
                        }

                        //set the total margin in the article width in the current case we take
                        //6 form right and 6 from left
                        float marginDistance = dpToPx(context, 12);

                        //get the article width
                        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
                        float dpWidth = (displayMetrics.widthPixels - marginDistance) / displayMetrics.density;
                        float viewWidth = displayMetrics.widthPixels - marginDistance;

                        //check if the image size is bigger than the screen dp
                        if (dpWidth > content.getWidth()) {
                            isImageSmallThanScreen = true;
                            viewWidth = dpToPx(context, content.getWidth());
                        }

                        //get the ratio
                        float ratio = (float) content.getWidth() / (float) content.getHeight();

                        //set the new view height
                        float viewHeight = viewWidth / ratio;

                        //set the image size for the place holder
                        holder.contentImage.getLayoutParams().height = (int) viewHeight;
                        holder.contentImage.getLayoutParams().width = (int) viewWidth;

                        //the radius to set to the corner of the view
                        float cornerRadius = dpToPx(context, 12);

                        //check the text is first or the last to put separator
                        if (holder.getAdapterPosition() == 1 || isUpSeparator) {
                            holder.contentContainer.setTopRightCornerRadius(cornerRadius);
                            holder.contentContainer.setTopLeftCornerRadius(cornerRadius);
                            holder.contentContainer.setBackgroundResource(R.drawable.up_separator_gray);
                        } else if (holder.getAdapterPosition() == article.getContent().size() || isDownSeparator) {
                            holder.contentContainer.setBottomLeftCornerRadius(cornerRadius);
                            holder.contentContainer.setBottomRightCornerRadius(cornerRadius);
                            holder.contentContainer.setBackgroundResource(R.drawable.down_separator_gray);
                        } else {
                            holder.contentContainer.setBackgroundColor(context.
                                    getResources().getColor(R.color.text_typo_background));
                        }

                        //load the article content image
                        boolean finalIsImageSmallThanScreen = isImageSmallThanScreen;
                        boolean finalIsUpSeparator = isUpSeparator;
                        boolean finalIsDownSeparator = isDownSeparator;
                        GlideApp.with(context)
                                .load(content.getImage().trim())
                                .transition(DrawableTransitionOptions.withCrossFade())
                                .diskCacheStrategy(DiskCacheStrategy.ALL)
                                .listener(new RequestListener<Drawable>() {
                                    @Override
                                    public boolean onLoadFailed(@Nullable GlideException e,
                                                                Object model, Target<Drawable> target,
                                                                boolean isFirstResource) {
                                        return false;
                                    }

                                    @Override
                                    public boolean onResourceReady(Drawable resource, Object model,
                                                                   Target<Drawable> target,
                                                                   DataSource dataSource,
                                                                   boolean isFirstResource) {
                                        holder.placeHolderImage.setVisibility(View.GONE);

                                        //if the image is small than the device width set the background to white
                                        if (finalIsImageSmallThanScreen) {
                                            if (holder.getAdapterPosition() == 1 || finalIsUpSeparator) {
                                                holder.contentContainer.setBackgroundResource(R.drawable.up_separator);
                                            } else if (holder.getAdapterPosition() == article.getContent().size() || finalIsDownSeparator) {
                                                holder.contentContainer.setBackgroundResource(R.drawable.down_separator);
                                            } else {
                                                holder.contentContainer.setBackgroundColor(Color.WHITE);
                                            }
                                        }
                                        return false;
                                    }
                                })
                                .into(holder.contentImage);
                    }
                }
            }
        }
    }

任何帮助都会很好......

2 个答案:

答案 0 :(得分:2)

我写了一个示例应用程序来执行此操作并进行测试。因此,如果您仍有问题,请随时下载。我至少会把它搁置一个月。

Sample Dynamic ViewHolders

我不确定您当前的代码是如何工作的。回收不会根据您的需要决定返回哪个视图以重新使用,除了通过覆盖getItemViewType并返回正确的视图来指定所需的视图,您只需获取下一个回收的视图。

我假设您正在尝试填充&#34;动态&#34;视图根据显示的模型而变化的内容列表。实际上是一种非常常见的情况。

但是,您正在检查循环(也称为bindView)上的viewModel类型并确定要执行的init。这似乎不正确。

想象一下,我有一个这样的列表:

  1. 标题(headerViewModel init)来查看1
  2. 文本项(itemViewModel init)以查看2
  3. 要查看3的图像项目(imageViewModel init)
  4. 然后你有一个像这样的动态项目列表:

    1. 标题
    2. 文本
    3. 图像
    4. 图像
    5. 文本
    6. 现在第一次通过它看起来很棒,因为你是第一次创建它们所以1到3是完美的。

      现在让我们想象一下,为了简单起见,你只回收3个视图及其小屏幕。

      当您向下滚动列表并在第4项中重复使用时,视图1会消失 新面貌是这样的:

      1. (第2项)视图2 = TextViewModel(正确显示)
      2. (第3项)视图3 = ImageViewModel(正确显示)
      3. (第4项)查看1(已回收)HeaderViewModel(绑定到图像)
      4. 因此,您可以看到在动态列表的回收中使用了错误的模型。

        要解决此问题,您的模型应该驱动所使用的支架,而不是上次回收时使用的类型。

        我想你会得到这张照片。如果这是有道理的,请告诉我。

        关键是让模型驱动视图类型而不是回收器,因为您的布局是动态的。好运。

答案 1 :(得分:0)

onBindViewHolder 返回viewHolder和position。您必须使用此位置将​​数据绑定到视图。

if (holder instanceof ImageViewHolder) {
        initImageViewHolder((HeaderViewHolder) holder, position);
    }

然后

private void initImageViewHolder(ImageViewHolder holder, int position) {
List<Content> contents = article.getContent();
Content content = contents.get(position);
// other statements
}

在呈现视图之前,不会更新视图的适配器位置。因此,您必须使用onBindViewHolder提供的位置。

以下是onBindViewHolder

的文档
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
     * position.
     * <p>
     * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
     * again if the position of the item changes in the data set unless the item itself is
     * invalidated or the new position cannot be determined. For this reason, you should only
     * use the <code>position</code> parameter while acquiring the related data item inside
     * this method and should not keep a copy of it. If you need the position of an item later
     * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
     * have the updated adapter position.
     *
     * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
     * handle efficient partial bind.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param position The position of the item within the adapter's data set.
    public abstract void onBindViewHolder(VH holder, int position);