web-dev-qa-db-ja.com

RecyclerViewの画像の奇妙な動作

Android.support.v7.widget.RecyclerViewを使用して、テキストと場合によっては画像を含むシンプルなアイテムを表示しています。基本的に私はここからチュートリアルに従いました https://developer.Android.com/training/material/lists-cards.html そしてmDatasetのタイプをString []からJSONArrayおよびxml ViewHolder。私の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>

このフラグメントでは、Webサーバーからすべての情報をロードしています。情報を受け取った後、RecyclerViewでアイテムを初期化します。 FragmentのonCreateメソッドで設定されたLinearLayoutManagerを使用します。アダプターを追加した後、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();
}
}

そして、RecyclerView内のビューの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で正しく表示されます。ただし、下にスクロールして再度上にスクロールしてリストの最後に到達すると、画像が含まれないアイテムに画像が表示されます。現時点では、9つのアイテムがあります。最後の2つには写真が含まれています。下にスクロールすると、最初の7つの項目にテキストが表示されます。リストの最後に到達すると、予想どおり2枚の写真が表示されます。しかし、もう一度上にスクロールすると、それらの写真は他のアイテムに表示されます。

上下にスクロールすると、常に同じ順序で画像が変更されます。

  • 最初に上にスクロールすると(一度ダウンした後)、2番目のアイテムに画像が作成されます。
  • 下にスクロールすると、7番目の項目に画像が作成されます。
  • 上にスクロールすると、最初のアイテムに画像が作成されます。
  • 下にスクロールしても何も作成されません。
  • 上にスクロールすると、3番目のアイテムの画像が作成され、2番目のアイテムの画像は他の画像で上書きされます。
  • 下にスクロールすると、6番目のアイテムの画像が作成され、7番目のアイテムの画像が消えます。
  • 等々 ...

OnBindViewHolder(ViewHolder holder、int position)メソッドは、アイテムが画面に再び表示されるたびに呼び出されることを既に発見しました。 mDatasetで位置と指定された情報を確認しました。ここではすべて正常に動作します。位置は正しく、指定された情報も正しいです。すべてのアイテムのテキストは変更されず、常に正しく表示されます。誰でもこの問題を抱えており、解決策を知っていますか?

34
Jason Saruulo

if ( object.has("image") )メソッドのonBindViewHolder(ViewHolder holder, int position)ステートメントにelse句を追加して、画像をnullに設定する必要があると思います。

holder.image.setImageDrawable(null);

onBindViewHolderのドキュメント これが必要な理由を最もよく説明していると思います。

指定された位置にデータを表示するために、RecyclerViewによって呼び出されます。このメソッドは、指定された位置のアイテムを反映するために、itemViewのコンテンツを更新する必要があります。

ViewHolderが渡されると、完全に更新する必要があると常に仮定する必要があります。

47
Joe

人々がこれに関してまだ問題を抱えているかどうかはわかりませんが、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);
            }

私はピカソを使用していますが、他の状況でも動作するはずです。これが誰かを助けることを願っています!

14
Lion789

@ Lion789の回答に基づきます。 Googleの Volley を画像とJSONの読み込みに使用しています。

これにより、後でリサイクルされるビュー上の位置を取得できます。

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

リクエストを行うとき、位置をタグとして設定します

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

リクエストをキャンセルしたボレーのギャランティは配信されません。

3
Marcola