web-dev-qa-db-ja.com

間違った値をとるRecyclerViewアダプター

RecyclerViewには2種類のViewsがあり、1つはユーザーパブリケーションを表し、もう1つはイベントパブリケーションを表します。両方に共通の要素があります。たとえば、タイムスタンプを示す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から拡張されています。リクエストを行ったり、アダプターをリロードしたりしません。アダプターを一度だけロードします。

更新:

ところで、Fragment内でこのRecyclerViewを使用しているのは、Fragment内のViewPagerで読み込まれているPageAdapterで読み込まれていますが、これが問題なのでしょうか?

更新:これは他のバインディングコードです。

これは、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と呼ばれる、私が持っているカスタムメソッドのデータセットとともに読み込まれますが、これは問題ではないようです。

スクリーンショットは次のとおりです。

アプリに初めて入力したときのリストのトップ:

enter image description here

それから私が戻ってくるとき: enter image description here

ちなみに、同じようなボタン、嫌いなボタン、お気に入りのボタンは、誰かがそれらを複数回クリックした場合、数値を表示しますが、この値もそうである場合は置き忘れられます。

PDATE:ネストされたフラグメントのためではないことがわかりました。各タブフラグメントが、アクティビティ内のViewPager内のPageStateAdapterにあるようにコードを変更しました。しかし、問題はまだそこにあります。

PDATE: getItemIdメソッドが実行されないことがわかりました。IDKはまだ実行されていません。

21
4gus71n

クラスの階層と使用方法を確認することをお勧めします。一般に、基本クラスで_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()メソッドを実装します。 両方のケースで必ずスーパークラスを呼び出してください。また、必ず設定してくださいeverybindViews()メソッドの特定のパブリケーションのレイアウトで表示します。

アダプターでは、データセット内のその位置のパブリケーションタイプに基づいて正しいホルダーを作成する必要があります。

_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));
    }
}
_
3
Larry Schiefer

これは通常、「if(field!= null)holder.setField(field)」のようなものがあり、elseがない場合に起こります。ホルダーはリサイクルされます。つまり、そこに値があるので、すべての値をクリーンアップまたは置換する必要があります。nullの場合はnullitし、そうでない場合は必ず記述する必要があります。遅いですが、他の人への答えとして。

67
Ivan

setHasStableIds(false)を設定して問題を解決しました。

6

異なる高さを持つ非同期ロード画像にも同じ問題がありました。したがって、デバッガーを使用すると、リサイクルの位置がビューの実際のサイズに依存することがわかります。

私にとって簡単な解決策は、さまざまなサイズを指定することでした。したがって、システムはすべてのアイテムの正確なサイズを認識します。 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は異なる/動的なアイテムサイズについて混乱しません。

1
everyman

バインディングコードの1つの大きな変数は、日付の書式設定にあります: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()));
    }
}

例としてEventPublicationViewHolderUserPublicationViewHolderに対して同じことを行います):

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と非常によく似たパターンを維持しているため、全体的なコードが少なくなるだけでなく、内部の一貫性も高まります。これは確かにそれを行う唯一の方法ではなく、特定のユースケースに基づいた提案にすぎません。

0
lopar