Подтвердить что ты не робот

Странное поведение изображений в 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>

В этом фрагменте я загружаю всю информацию с веб-сервера. После получения информации я инициализирую элементы в RecyclerView. Я использую LinearLayoutManager, который установлен в методе onCreate моего фрагмента. После добавления адаптера я вызываю метод setHasFixedSize (true) в моем RecyclerView. Класс My 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 для просмотра внутри моего 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, int position) вызывается каждый раз, когда элемент появляется снова на экране. Я проверил позицию и данные в моем mDataset. Здесь все прекрасно работает: позиция правильная и данная информация тоже. Текст во всех элементах не изменяется и всегда отображается правильно. Кто-нибудь есть какая-то проблема и знает какое-то решение? Любая помощь высоко ценится.:)

4b9b3361

Ответ 1

Я думаю, вам нужно добавить предложение else в ваш оператор if ( object.has("image") ) в методе onBindViewHolder(ViewHolder holder, int position), установив для изображения значение null:

holder.image.setImageDrawable(null);

Я думаю, документация для onBindViewHolder лучше всего описывает, почему это необходимо:

Вызывается RecyclerView для отображения данных в указанной позиции. Этот метод должен обновлять содержимое элемента itemView, чтобы отразить элемент в данной позиции.

Вы всегда должны предполагать, что ViewHolder прошел в необходимом для полного обновления:)

Ответ 2

Не уверен, что у людей все еще есть проблемы с этим, но оператор if не работает для меня. Для меня это работало:

Использование переопределения этого метода в адаптере

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

за которым следует вызов оператора if, основанного на этом для самого изображения внутри onBindViewHolder, например:

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, но он должен работать с любой другой ситуацией. Надеюсь, это поможет кому-то!

Ответ 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);
}

Гарантии волейбола, которые отменяют запросы, не будут доставлены.