【问题标题】:Strange behaviour of images in RecyclerViewRecyclerView 中图像的奇怪行为
【发布时间】:2015-02-28 20:30:37
【问题描述】:

我正在使用 android.support.v7.widget.RecyclerView 显示包含文本的简单项目,在某些情况下还显示图像。基本上我从这里https://developer.android.com/training/material/lists-cards.html 开始遵循教程,只是将 mDataset 的类型从 String[] 更改为 JSONArray 和 ViewHolder 的 xml。我的 RecyclerView 位于一个简单的 Fragment 中:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
    android:id="@+id/my_hopps_recycler_view"
    android:scrollbars="vertical"
    android:layout_centerInParent="true"
    android:layout_width="200dp"
    android:layout_height="wrap_content"/>

</RelativeLayout>

在这个片段中,我从网络服务器加载所有信息。收到信息后,我初始化 RecyclerView 中的项目。我使用在我的 Fragment 的 onCreate 方法中设置的 LinearLayoutManager。添加 Adapter 后,我在 RecyclerView 上调用 setHasFixedSize(true) 方法。我的适配器类如下所示:

import android.graphics.BitmapFactory;
import android.support.v7.widget.RecyclerView;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.apache.commons.lang3.StringEscapeUtils;
import org.json.JSONArray;
import org.json.JSONObject;

import saruul.julian.MyFragment;
import saruul.julian.R;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

/**
* Fragment holding the RecyclerView.
*/
private MyFragment myFragment;
/**
* The data to display.
*/
private JSONArray mDataset;

public static class ViewHolder extends RecyclerView.ViewHolder {

    public TextView text;
    ImageView image;

    public ViewHolder(View v) {
        super(v);
        text = (TextView) v.findViewById(R.id.my_view_text);
        image = (ImageView) v.findViewById(R.id.my_view_image);
    }
}

public MyAdapter(MyFragment callingFragment, JSONArray myDataset) {
    myFragment = callingFragment;
    mDataset = myDataset;
}

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.my_view, parent, false);
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    try {
        JSONObject object = mDataset.getJSONObject(position);
        if ( object.has("image") ){
            if ( !hopp.getString("image").equals("") ){
                try {
                    byte[] decodedString = Base64.decode(StringEscapeUtils.unescapeJson(object.getString("image")), Base64.DEFAULT);
                    holder.image.setImageBitmap(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length));
                    holder.image.setVisibility(View.VISIBLE);
                } catch ( Exception e ){
                    e.printStackTrace();
                }
            } 
        }
        if ( object.has("text") ){
            holder.text.setText(object.getString("text"));
        }
    } catch ( Exception e ){
        e.printStackTrace();
    }
}

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
    return mDataset.length();
}
}

我的 xml 用于我的 RecyclerView 中的视图:

<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="@dimen/activity_horizontal_margin">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:id="@+id/my_hopp_people_reached"/>

    <ImageView
        android:id="@+id/my_hopp_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:adjustViewBounds="true"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:id="@+id/my_hopp_text"/>

    <ImageButton
        android:id="@+id/my_hopp_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@null"
        android:src="@drawable/ic_action_discard"
        />

</LinearLayout>

</android.support.v7.widget.CardView>

所以现在我的问题是:一切正常。文本和图片在我的 RecyclerView 中正确显示。但是,如果我通过向下滚动并再次向上滚动到达列表末尾,则图片将显示在不应包含图片的项目中。 目前我有九个项目。其中最后两个包含一张图片。所以我向下滚动,只看到前七个项目中的文本。到达列表末尾时,我按预期显示了两张图片。但是,如果我再次向上滚动,这些图片会出现在其他项目中。

向上和向下滚动总是以相同的顺序更改图片:

  • 第一次向上滚动(向下滚动一次后)会在第二项中创建一张图片。
  • 向下滚动会在第 7 项中创建图片。
  • 向上滚动会在第一项中创建一张图片。
  • 向下滚动不会产生任何效果。
  • 向上滚动会在第三项中创建一张图片,而第二项中的图片被另一张图片覆盖。
  • 向下滚动会在第 6 项中创建一张图片,并让第 7 项中的图片消失。
  • 等等……

我已经发现每次屏幕上再次出现项目时都会调用 onBindViewHolder(ViewHolder holder, int position) 方法。我检查了我的 mDataset 中的位置和给定信息。这里一切正常:位置正确,给定的信息也是。所有项目中的文本都不会更改,并且始终正确显示。 有没有人遇到过这种问题并知道一些解决方案?

【问题讨论】:

    标签: android imageview arrays android-recyclerview android-cardview


    【解决方案1】:

    基于@Lion789 的回答。 我正在使用 Google 的 Volley 进行图像和 json 加载。

    这允许我们检索视图上的位置,稍后将被回收。

    @Override
    public int getItemViewType(int position) {
        return position;
    }
    

    发出请求时,将位置设置为TAG

    @Override
    public void onBindViewHolder(final ItemHolder holder, int position) {
            ImageRequest thumbnailRequest = new ImageRequest(url,
                    new Response.Listener<Bitmap>() {
                        @Override
                        public void onResponse(Bitmap response) {                          
                            holder.itemThumbnail.setImageBitmap(response);
                        }
                    },[...]);
    
            thumbnailRequest.setTag(String.valueOf(position)); //setting the TAG
            mQueue.addToRequestQueue(thumbnailRequest);
    }
    

    当视图被回收时,我们将取消请求,并删除当前图像

    @Override
    public void onViewRecycled(ItemHolder holder) {
        super.onViewRecycled(holder);
        mQueue.cancelAll(String.valueOf(holder.getItemViewType()));
        holder.itemThumbnail.setImageDrawable(null);
    }
    

    取消请求的 Volley 保证将不会交付。

    【讨论】:

    • 这是否会在每次视图返回视图时调用图像?
    • 是的,但是由于 Volley 缓存了所有内容,因此图像已经在内存中
    【解决方案2】:

    不确定人们是否对此仍有疑问,但 if 语句对我不起作用。对我有用的是:

    在适配器中使用此方法的覆盖

    @Override
        public int getItemViewType(int position) {
            return position;
        }
    

    然后在 onBindViewHolder 中基于 this 为图像本身调用 if 语句,如下所示:

    int pos = getItemViewType(position);
                if(theList.get(pos).getPicture() == null) {
    
                    holder.picture.setVisibility(View.GONE);
                } else {
    
                    Picasso.with(context).load(R.drawable.robot).into(holder.picture);
                }
    

    我正在使用 Picasso,但它应该适用于任何其他情况。希望这对某人有帮助!

    【讨论】:

    • 添加 getItemViewType 解决了我的问题!
    • 这不能被视为一种解决方案。你基本上告诉RecyclerView根本不要回收。
    【解决方案3】:

    我认为您需要在onBindViewHolder(ViewHolder holder, int position) 方法中的if ( object.has("image") ) 语句中添加一个else 子句,将图像设置为null:

    holder.image.setImageDrawable(null);
    

    我认为the documentation for onBindViewHolder 最好地描述了为什么这是必要的:

    由 RecyclerView 调用,在指定位置显示数据。此方法应更新 itemView 的内容以反映给定位置的项目。

    您应该始终假设传入的 ViewHolder 需要完全更新:)

    【讨论】:

    • 这似乎可以解决问题!我在 if ( !hopp.getString("image").equals("") ) 语句中添加了一个 else 子句,因为我的 JSONObjects 中总会有一个 image 字段。我尝试了同样的事情,但使用了 holder.image.setImageBitmap(null) 显然不起作用,或者我也把它放在了错误的 if 语句中。没关系,因为你拯救了我的一天。 :)
    • 谢谢。我修改为holder.image.setImageResource(R.drawable.image);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-29
    • 1970-01-01
    • 1970-01-01
    • 2012-06-16
    • 2021-06-06
    • 1970-01-01
    相关资源
    最近更新 更多