RecyclerView中图像的奇怪行为

时间:2015-01-02 13:58:25

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

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

<?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中的项目。我使用了一个LinearLayoutManager,它在我的Fragment的onCreate方法中设置。添加适配器后,我在RecyclerView上调用setHasFixedSize(true)方法。我的Adapter类看起来像这样:

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:

<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中检查了位置和给定的信息。这里的一切都很好:位置是正确的,也是给定的信息。所有项目中的文本都不会更改,并始终正确显示。 有没有人有任何这种问题,并且知道一些解决方案?

3 个答案:

答案 0 :(得分:46)

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

holder.image.setImageDrawable(null);

我认为the documentation for onBindViewHolder最能说明为什么这是必要的:

  

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

你应该总是假设传入的ViewHolder需要完全更新:)

答案 1 :(得分:14)

不确定人们是否仍有这方面的问题,但if语句对我不起作用。对我有用的是:

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

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

然后在onBindViewHolder中为图像本身调用基于此的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);
            }

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

答案 2 :(得分:3)

基于@ 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 garanties将无法发送。