Адаптер RecyclerView принимает неправильные значения

У меня есть RecyclerView, который показывает два типа View, один из которых представляет публикацию пользователя, а другой представляет публикацию события. У обоих есть общие элементы, например TextView, который показывает отметку времени. Итак, я создал PublicationViewHolder, который принимает эту отметку времени TextView в переменную и загружает ее. Моя проблема в том, что адаптер изначально загружает правильные значения, но когда я прокручиваю вниз и снова прокручиваю вверх, значения в позициях заменяются значениями из других позиций. Вот код:

public class PublicationViewHolder extends RecyclerView.ViewHolder {

    private TextView vTimeStamp;

    public PublicationViewHolder(View itemView) {
        super(itemView);
        this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp);
    }

    public void load(Publication publication, int i) {
        load(publication);
        try {
            if (Publication.TYPE_USER_PUBLICATION == publication.getType()) {
                load((UserPublication) publication);
            } else if (Publication.TYPE_EVENT_PUBLICATION == publication.getType()) {
                load((EventPublication) publication);
            }
        } catch (ClassCastException e) {
            throw new RuntimeException("Publication type cast fail. See PublicationViewHolder.");
        }
    }

    public void load(Publication publication) {
        vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }

    public void load( UserPublication publication) {
        //This method is override by UserPublicationViewHolder
    };

    public void load( EventPublication publication) {
        //This method is override by EventPublicationViewHolder
    };

}

Теперь я буду делать свои UserPublicationViewHolder публикации только для пользователей.

public class UserPublicationViewHolder extends PublicationViewHolder {
    private  ImageView vImageView, vLikeButton, vDislikeButton, vFavButton, vEditPost, vDeletePost;
    private  TextView vText, vUsername, vLikeCount, vDislikeCount, vFavCount;
    private  PostImagesLayout vImagesContainer;
    private TagCloudLocationFriends tagsView;

    public UserPublicationViewHolder(View itemView) {
        super(itemView);
        vImageView = (ImageView) itemView.findViewById(R.id.img_view_publication_user);
        vText = (TextView) itemView.findViewById(R.id.txt_view_publication_text);

        vLikeCount = (TextView) itemView.findViewById(R.id.txt_view_like_count);
        vFavCount = (TextView) itemView.findViewById(R.id.txt_view_fav_count);
        vDislikeCount = (TextView) itemView.findViewById(R.id.txt_view_dislike_count);

        vUsername = (TextView) itemView.findViewById(R.id.txt_view_publication_user_name);
        vLikeButton = (ImageView) itemView.findViewById(R.id.img_view_like);
        vDislikeButton  = (ImageView) itemView.findViewById(R.id.img_view_dislike);
        vFavButton  = (ImageView) itemView.findViewById(R.id.img_view_fav);
        vImagesContainer = (PostImagesLayout) itemView.findViewById(R.id.container_post_images);

        tagsView = (TagCloudLocationFriends) itemView.findViewById(R.id.location_friends_tag);

        // edit - remove icons
        vDeletePost = (ImageView) itemView.findViewById(R.id.img_view_delete_post);
        vEditPost = (ImageView) itemView.findViewById(R.id.img_view_edit_post);
    }


    @Override
    public void load(UserPublication publication) {
        //Load the UserPublicationViewHolder specific views.
    }
}

Теперь буду делать то же самое, но для событийных публикаций.

public class EventPublicationViewHolder extends PublicationViewHolder {

    private TextView vTextViewTitle;
    private TextView vTextViewText;

    public EventPublicationViewHolder(View itemView) {
        super(itemView);
        vTextViewTitle = (TextView) itemView.findViewById(R.id.txt_view_publication_event_title);
        vTextViewText = (TextView) itemView.findViewById(R.id.txt_view_publication_event_text);
    }

    @Override
    public void load(EventPublication publication) {
        //Load the EventPublicationViewHolder specifics views
    }
}

Теперь вот мой адаптер RecyclerView:

public class PublicationAdapter extends RecyclerView.Adapter<PublicationViewHolder> {

    public static final int USER_PUBLICATION_TYPE = 1;
    public static final int EVENT_PUBLICATION_TYPE = 2;
    private List<Publication> publications = new ArrayList<Publication>();

    public List<Publication> getPublications() {
        return publications;
    }

    public void setPublications(List<Publication> publications) {
        this.publications = publications;
    }

    @Override
    public int getItemViewType(int position) {
        if (publications.get(position) instanceof UserPublication) {
            return USER_PUBLICATION_TYPE;
        }
        if (publications.get(position) instanceof EventPublication) {
            return EVENT_PUBLICATION_TYPE;
        }
        throw new RuntimeException("Unknown view type in PublicationAdapter");
    }

    @Override
    public PublicationViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
        View v;
        switch (type) {
            case USER_PUBLICATION_TYPE:
                v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_user_publication, viewGroup, false);
                return new UserPublicationViewHolder(v);
            case EVENT_PUBLICATION_TYPE:
                v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_event_publication, viewGroup, false);
                return new EventPublicationViewHolder(v);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(PublicationViewHolder aPublicationHolder, int i) {
        aPublicationHolder.load(publications.get(i), i);
    }

    @Override
    public long getItemId(int position) {
        //Here I tried returning only position or 0 without luck.
        //The id is unique BTW
        return publications.get(position).getId();
    }

    @Override
    public int getItemCount() {
        return publications.size();
    }

}

Я не знаю, что может быть не так, UserPublication и EventPublication расширяются от Publication. Я не делаю какой-то запрос или перезагружаю адаптер. Я загружаю адаптер только один раз.

Обновление:

Кстати, я использую этот RecyclerView внутри фрагмента, который загружается в PageAdapter, который загружается в ViewPager, который находится внутри фрагмента, может быть, это проблема?

Обновление: это другой код привязки.

Это метод загрузки файла UserPublicationViewHolder.

    @Override
    public void load(UserPublication publication) {
        PicassoHelper.publicationUser(getActivity(), publication.getUser().getAvatarUrl(),
                vImageView);
        vText.setText(publication.getText());
        vUsername.setText(publication.getUser().getName());
        boolean hasLocation = false;
        if (publication.getImages().length > 0) {
            vImagesContainer.setImages(publication.getImages());
        } else {
            vImagesContainer.setVisibility(View.GONE);
        }
        tagsView.setTags(new ArrayList<MinikastTag>());
        tagsView.drawTags();

        if(publication.getLocation() != null || publication.getTaggedFriends().size() > 0){
            if(publication.getLocation() != null){
                hasLocation = true;
                tagsView.add(new MinikastTag(1,"Post from ",1));
                tagsView.add(new MinikastTag(2, publication.getLocation().getName(), 2));
            }
            if(publication.getTaggedFriends().size() > 0){
                if(hasLocation)
                    tagsView.add(new MinikastTag(3," with ",1));
                else
                    tagsView.add(new MinikastTag(3,"With ",1));

                int i = 0;
                for(User aUser: publication.getTaggedFriends()){
                    MinikastTag aTag;
                    if(i == publication.getTaggedFriends().size() - 1 ) {
                        aTag = new MinikastTag(4, aUser.getName(), 3);
                        aTag.setUserID(aUser.getId());
                        aTag.setUserName(aUser.getName());
                        tagsView.add(aTag);
                    } else {
                        aTag = new MinikastTag(4, aUser.getName() + ", ", 3);
                        aTag.setUserID(aUser.getId());
                        aTag.setUserName(aUser.getName());
                        tagsView.add(aTag);
                    }
                    i = i+1;
                }
            }
        }
        tagsView.drawTags();

        // likes, dislikes, favs
        if(publication.getLikesAmount() > 0)
            vLikeCount.setText(String.valueOf(publication.getLikesAmount()));

        if(publication.getDislikesAmount() > 0)
            vDislikeCount.setText(String.valueOf(publication.getDislikesAmount()));

        if(publication.getLovesAmount() > 0)
            vFavCount.setText(String.valueOf(publication.getLovesAmount()));

        // reset buttons
        vFavButton.setPressed(false);
        vDislikeButton.setPressed(false);
        vLikeButton.setPressed(false);

        if(publication.getRelationship().equals("LOVE"))
            vFavButton.setPressed(true);
        else if (publication.getRelationship().equals("LIKE"))
            vLikeButton.setPressed(true);
        else if (publication.getRelationship().equals("DISLIKE"))
            vDislikeButton.setPressed(true);

        // edit - remove icons

        if(String.valueOf(publication.getUser().getId()).equals(StartupSharedPreferences.getProfileId())){
            vEditPost.setVisibility(View.VISIBLE);
            vDeletePost.setVisibility(View.VISIBLE);
        }else{
            vEditPost.setVisibility(View.INVISIBLE);
            vDeletePost.setVisibility(View.INVISIBLE);
        }
    }
}

А это метод загрузки EventPublicationViewHolder:

@Override
public void load(EventPublication publication) {
    vTimeStamp.setVisibility(View.GONE);
    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //GoTo.eventDetail(getActivity(), publication);
        }
    });
    vTextViewTitle.setText(publication.getTitle());
    vTextViewText.setText(publication.getText());
}

Я прокомментировал часть кода только потому, что тестировал его, но, как видите, я делаю только setTexts и оцениваю некоторые изображения.

А вот так я устанавливаю адаптер, LinearLayoutManager и т.д. в методе onViewCreated фрагмента.

vRecyclerView = (FixedRecyclerView) view.findViewById(R.id.recycler_view_publications);
        vSwipeRefresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container);
        mFeedCallback.onScrollReady(vRecyclerView);
        mLayoutManager = buildLayoutManager();
        vRecyclerView.setLayoutManager(mLayoutManager);
        vRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
        mAdapter = new PublicationAdapter();
        vSwipeRefresh.setOnRefreshListener(this);
        vSwipeRefresh.setColorSchemeResources(R.color._SWIPER_COLOR_1, R.color._SWIPER_COLOR_2,
                R.color._SWIPER_COLOR_3, R.color._SWIPER_COLOR_4);
        vRecyclerView.setAdapter(mAdapter);

Кстати, адаптер загружается с набором данных в пользовательском методе, который у меня есть, который называется onHttpClientReady, но, похоже, это не проблема.

Вот несколько скриншотов:

Вверху списка, когда я впервые захожу в приложение:

введите здесь описание изображения

Затем, когда я вернусь: введите здесь описание изображения

Кстати, кнопки «Нравится», «Не нравится» и «Избранное», если кто-то щелкнул их более одного раза, отобразят числовое значение, эти значения также неуместны, если они есть.

ОБНОВЛЕНИЕ: Теперь я знаю, что это произошло не из-за вложенных фрагментов. Я изменил свой код таким образом, что теперь каждый фрагмент вкладки находится в PageStateAdapter, который находится внутри ViewPager, который находится внутри Activity. Но проблема все еще существует.

ОБНОВЛЕНИЕ: я обнаружил, что метод getItemId никогда не выполняется, почему пока.


person 4gus71n    schedule 19.01.2015    source источник
comment
Отметьте public void load(Publication publication, int i) — вы никогда не используете i   -  person Alexander Zhak    schedule 19.01.2015
comment
Я так не думаю. Это правда, параметр int i бесполезен, мой плохой. Но экземпляры публикации на самом деле правильные, как вы можете видеть в методе onBindViewHolder. Я обновил свой ответ, я думаю, что проблема исходит с этой стороны. Я даже безуспешно пытался поставить все эти параметры как окончательные.   -  person 4gus71n    schedule 19.01.2015
comment
если пользователь нажимает на лайк, я получаю ответ с обновленным количеством лайков, в то время как установленный вид на эту позицию не обновляется при правильном просмотре Android   -  person Harsha    schedule 02.09.2016
comment
что такое метод getIds в getItemId?? как это получить.   -  person Vikash Sharma    schedule 06.04.2018


Ответы (5)


Я бы предложил пересмотреть иерархию классов и их использование. В общем, если вы выполняете операцию типа type == type в базовом классе, вы нарушаете цель абстракции и наследования. Что-то вроде этого будет работать для вас:

public abstract class PublicationViewHolder extends RecyclerView.ViewHolder {
    private TextView mTimeStamp;

    public PublicationViewHolder(View itemView) {
        mTimeStamp = (TextView)itemView.findViewById(R.id. txt_view_publication_timestamp);
    }

    public void bindViews(Publication publication) {
        mTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }
}

Теперь ваши "события" или "публикации пользователей" просто наследуются от этого класса и реализуют конструктор и метод bindViews(). Обязательно вызывайте суперкласс в обоих случаях. Кроме того, убедитесь, что вы установили каждое представление в макете для конкретной публикации в ваши bindViews() методы.

В вашем адаптере вам просто нужно создать правильный держатель на основе типа публикации в этой позиции в вашем наборе данных:

public class PublicationAdapter extends RecyclerView.Adapter {
    private ArrayList<Publication> mPubs;

    //  Your other code here, like
    //  swapPublications(), getItemCount(), etc.
    ...

    public int getItemViewType(int position) {
        return mPubs.get(position).getType();
    }

    public PublicationViewHolder createViewHolder(ViewGroup parent, int type) {
        PublicationViewHolder ret;
        View root;
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        if (type == USER_PUBLICATION_TYPE) {
            root =
                inflater.inflate(R.layout.view_holder_user_publication,
                    parent,
                    false);

            ret = new UserPubHolder(root);
        } else {
            root =
                inflater.inflate(R.layout.view_holder_event_publication,
                    parent,
                    false);

            ret = new EventPubHolder(root);
        }

        return ret;
    }

    public bindViewHolder(PublicationViewHolder holder, int position) {
        holder.bindViews(mPubs.get(position));
    }
}
person Larry Schiefer    schedule 19.01.2015
comment
Спасибо, я знаю, что мой код довольно беспорядочный, рефакторинг, который вы предлагаете, действительно хорош, но основная проблема заключается в том, что по какой-то причине, когда я прокручиваю вниз с помощью RecyclerView и снова поднимаюсь, данные отображаются в каждом RecyclerView. элемент меняется на некоторые другие данные из другого элемента другой позиции. Я не понимаю, как этот рефакторинг может помочь мне с моей проблемой. Как я уже сказал в своем обновлении, я думаю, что эта проблема связана с вложенными фрагментами. Ты согласен? - person 4gus71n; 19.01.2015
comment
Нет, это не вложенные фрагменты. Скорее всего так и делается привязка. Обычно это происходит потому, что представления перерабатываются, а операция привязки не устанавливает для всех представлений в иерархии состояние, соответствующее данным. - person Larry Schiefer; 20.01.2015
comment
Начните свой рефакторинг с малого: делайте только публикации пользователей, следуя шаблону, который я описал выше. Как только вы сделаете это правильно, вы добавите другой тип публикации и убедитесь, что все работает правильно. - person Larry Schiefer; 20.01.2015

Обычно это происходит, когда у вас есть что-то вроде "if (field!= null)holder.setField(field)" без else. Держатель переработан, это означает, что в нем будут значения, поэтому вам нужно очистить или заменить КАЖДОЕ значение, если оно равно нулю, вы должны обнулить, если это не так, вы должны написать это, ВСЕГДА. Поздновато, но, как ответ для других.

person Ivan    schedule 18.05.2015
comment
Благодарю вас! Ты сделал мой день!) - person Yazon2006; 18.01.2016
comment
Это определенно должен быть принятый ответ. Я даже не знал, что это вообще существует, и теперь, когда я смотрю на свой проект, это объясняет множество крошечных ошибок, которые я не мог легко воспроизвести. Спасибо за подсказку Иван - person PGMacDesign; 24.02.2016
comment
Это оно. В основном у меня был блок else. У меня было 2 типа представления, оба из которых имеют одинаковые макеты и элементы. Блок if скрывает элемент, для которого по умолчанию установлено значение visible. Блок else просто заполняет элемент, который я уже показывал. В соответствии с этим ответом я должен снова установить элемент в visible в блоке else, несмотря на то, что он уже visible в XML. И заработало, все нормально загрузилось, даже после перепрошивки. - person SergeantPeauts; 09.03.2016
comment
Спасибо, это действительно решило мою проблему - person Aayushi; 17.03.2016
comment
@Ivan У меня может быть аналогичная проблема здесь: stackoverflow.com/questions/43531900/ Буду признателен за любые мысли и идеи о том, как исправить. - person AJW; 24.04.2017
comment
Если это значение null, почему я должен снова обнулить поле? Предположим, поле получило неправильное значение (например, ноль), если я оставлю его нулевым, оно продолжит получать неправильное значение. Я прав? Итак, мой вопрос: получают ли все поля правильные значения в начале, и по мере повторного использования держателя эти значения меняются? Кто-нибудь может это подтвердить? - person ; 19.11.2017
comment
@EricaOkamura значения всегда будут меняться. При каждом повторном использовании ваш метод держателя представления будет выполняться и изменять значения (если вы, конечно, его кодируете). Таким образом, вы никогда не узнаете, является ли значение нулевым или нет, если вы не проверите его, поэтому вы можете заменить все значения. - person Ivan; 20.11.2017

для меня установка setHasStableIds(false) решила проблему.

person fire in the hole    schedule 26.09.2015

Была такая же проблема с асинхронно загружаемыми изображениями, которые имели разную высоту. Таким образом, с помощью отладчика вы могли видеть, что позиции для повторного использования зависят от фактического размера представления.

Простым решением для меня было указать разные размеры, чтобы система знала точный размер всех предметов. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

Например, пейзаж, портрет и квадрат.

Поэтому я создал отдельные представления и использовал их так: (упрощенно)

public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
  // ...
  public static class ViewHolderLandscape extends RecyclerView.ViewHolder { ... }
  public static class ViewHolderPortrait  extends RecyclerView.ViewHolder { ... }
  public static class ViewHolderSquare    extends RecyclerView.ViewHolder { ... }

  @Override
  public int getItemViewType(int position) {      
    return mDataset.get(position).getImageType();
  }

  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    int mLayoutId = 0;

    switch (viewType) {
        case 0:
            mLayoutId = R.layout.list_item_landscape;
            break;
        case 1:
            mLayoutId = R.layout.list_item_portrait;
            break;
        case 2:
            mLayoutId = R.layout.list_item_square;
            break;
    }

    View v = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);        
    ButterKnife.inject(this, v);

    return new ViewHolder(v);
  }
}

Наконец, RecycleView не путается в размерах различных/динамических элементов.

person everyman    schedule 24.06.2015

Одна большая переменная в вашем коде привязки находится в вашем форматировании даты: DateFormatter.getTimeAgo(publication.getTimeStamp())

Не видя этот класс напрямую, трудно сказать наверняка, но похоже, что если временная метка неизменна, а средство форматирования основано на текущем времени, то это будет соответствовать изменению текста при восстановлении представления.

Я думаю, что более серьезной проблемой (и отчасти в стороне) является читабельность кода, что затрудняет визуальное обнаружение проблемы. Шаблон наследования и перегрузки затрудняют анализ кода и принятие решения о том, какой путь выбран и правильно ли он работает. Вот некоторый код салфетки (не собирал и не запускал его), используя более композиционный подход, который может быть более четкой организацией и упростить отладку проблем:

Новый вспомогательный класс для общего кода держателя представления, заменяет PublicationViewHolder:

public class PublicationViewHolderHelper {
    private final TextView vTimeStamp;

    public PublicationViewHolder(View itemView) {
        super(itemView);
        this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp);
    }

    /** Binds view data common to publication types. */
    public void load(Publication publication) {
        vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
    }
}

EventPublicationViewHolder в качестве примера (сделайте то же самое для UserPublicationViewHolder):

public class EventPublicationViewHolder extends ViewHolder {
    private final PublicationViewHolderHelper helper;

    // View fields...

    public EventPublicationViewHolder(View itemView) {
         super(itemView);
         helper = new PublicationViewHolderHelper(itemView);
         // Populated view fields...
    }

    @Override
    public void load(EventPublication publication) {
        helper.load(publication);
        //Load the EventPublicationViewHolder specifics views
    }
}

Обратите внимание, что в вашем адаптере теперь нет базового класса, а также нет необходимости в проверке типов, поэтому кода стало намного меньше.

Теперь адаптер остается прежним, за исключением универсального типа и onBindViewHolder:

public class PublicationAdapter extends RecyclerView.Adapter<ViewHolder> {
    ...
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        final Publication publication = publications.get(position);
        final int viewType = getItemViewType(position);
        switch (viewType) {
            case USER_PUBLICATION_TYPE:
                ((UserPublicationViewHolder) viewHolder).load((UserPublication) publication);
                break;
            case EVENT_PUBLICATION_TYPE:
                ((EventPublicationViewHolder) viewHolder).load((EventPublication) publication);
                break;
            default:
                // Blow up in whatever way you choose.
        }
    }
    ...
}

Обратите внимание, что он поддерживает шаблон, очень похожий на ваш onCreateViewHolder, так что не только меньше общего кода, но и больше внутренней согласованности. Это, конечно, не единственный способ сделать это, просто предложение, основанное на вашем конкретном случае использования.

person lopar    schedule 19.01.2015
comment
Хороший рефакторинг, но отметка времени — не единственные данные, которые я связываю. В методе load метода UserPublicationViewHolder я привязываю количество лайков публикации, дизлайков, избранного и т. д. Все эти поля, все эти данные полностью перепутываются, когда я прокручиваю вниз с помощью RecyclerView. Как вы думаете, может быть, это из-за вложенных фрагментов? Когда я добавляю фрагмент, все данные в порядке, все выглядит хорошо, затем, когда я прокручиваю вниз и возвращаюсь к началу Recycler, все данные перепутались. - person 4gus71n; 20.01.2015
comment
Тогда было бы полезно увидеть другой код привязки. Из первоначального вопроса казалось, что устанавливалось только одно текстовое представление. Это может дать некоторые подсказки. Кроме того, можете ли вы опубликовать фрагмент, который вы используете для установки менеджера компоновки и адаптера на RV? - person lopar; 20.01.2015