RecyclerView's row is shuffling and change Images : android

Abhishek picture Abhishek · Nov 7, 2015 · Viewed 7.1k times · Source
  • Changes in Question as other issue is fixed.

My issue is when Recycler View is scrolling Up and Down

  • Row item changes it's position when scrolling fast and get back to it's original position.
  • Row item makes it's some portion fix as background

Here how i am setting My Recycler view in Fragment.

private void setRecyclerView() {
    recyclerView.setHasFixedSize(true);
    StaggeredGridLayoutManager layoutManager =
            new StaggeredGridLayoutManager(2, 1);
    recyclerView.setLayoutManager(layoutManager);
    adapter = new TwitterTweetAdapter(getActivity());

    // setting every thing false in item animator 
    recyclerView.setItemAnimator(new RecyclerView.ItemAnimator() {

        @Override
        public void runPendingAnimations() {

        }

        @Override
        public boolean animateRemove(RecyclerView.ViewHolder viewHolder) {
            return false;
        }

        @Override
        public boolean animateAdd(RecyclerView.ViewHolder viewHolder) {
            return false;
        }

        @Override
        public boolean animateMove(RecyclerView.ViewHolder viewHolder, int i, int i2, int i3, int i4) {
            return false;
        }

        @Override
        public boolean animateChange(RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder viewHolder2, int i, int i2, int i3, int i4) {
            return false;
        }

        @Override
        public void endAnimation(RecyclerView.ViewHolder viewHolder) {

        }

        @Override
        public void endAnimations() {

        }

        @Override
        public boolean isRunning() {
            return false;
        }
    });
    recyclerView.setAdapter(adapter);
}

Here is My Adapter Class

In My Adapter I am setting One Header

//My ViewHolders

class Header extends RecyclerView.ViewHolder {

    @Bind(R.id.feed_head)
    CardView feedHeader;
    @Bind(R.id.feed_header_count)
    TextView userCount;
    @Bind(R.id.feed_header_text)
    TextView userText;

    public TwitterHeader(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}

public static class RowViewHolder extends RecyclerView.ViewHolder {

    @Bind(R.id.feed_row_view)
    CardView feedRow;
    @Bind(R.id.feed_image)
    ImageView feedImage;
    @Bind(R.id.video_play_icon)
    ImageButton playButton;
    @Bind(R.id.feed_description)
    TextView feedDescription;
    @Bind(R.id.feed_user_image)
    ImageView userImage;
    @Bind(R.id.feed_user_name)
    TextView userName;
    @Bind(R.id.feed_time)
    TextView feedDate;
    @Bind(R.id.feed_progress_bar)
    ProgressBar progressBar;

    public RowViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}

// declaration of variables

private static final int TYPE_HEADER = 0;
private static final int TYPE_FEED = 1;

Context context;
private Activity activity;
private List<MockData> dataLists;
public static List<MockData> dataListsupdated;

int userSearchCount;

// constructor

public FeedAdapter(Activity activity) {
    this.activity = activity;
}

// setting data list

public void setDataList(List<MockData> dataLists, int userSearchCount, float density) {
    this.dataLists = dataLists;
    this.density = density;
    this.userSearchCount = userSearchCount;
}

// onCreateViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    if (viewType == TYPE_HEADER) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.feed_header, viewGroup, false);
        return new Header(view);
    } else {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.feed_row, viewGroup, false);
        return new RowViewHolder(v);
    }
}

// onBindViewHolder i am bind my view with data.

if (holder instanceof Header) {
        Header header = (Header) holder;
        if (userSearchCount == 20) {
            header.userCount.setText(R.string.twitter_default_count);
        } else {
            header.userCount.setText(userSearchCount);
        }
        header.userText.setText(R.string.user);
        header.feedHeader.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(activity, TwitterSearchActivity.class);
                activity.startActivity(intent);
            }
        });

    } else {
        if (holder instanceof RowViewHolder) {
            final RowViewHolder rowViewHolder = (RowViewHolder) holder;
            final MockData responseList = dataLists.get(position);

            rowWidth = rowViewHolder.feedImage.getWidth();
            rowViewHolder.playButton.setVisibility(View.GONE);

            if (responseList.text.startsWith("RT")) {
                rowViewHolder.feedRow.setVisibility(View.GONE);
                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
                params.setMargins(0, 0, 0, 0);
                rowViewHolder.feed.setLayoutParams(params);
            } else {
                setRowImage(rowViewHolder, responseList);
                rowViewHolder.feedDescription.setText(responseList.text);

                rowViewHolder.userImage.setVisibility(View.VISIBLE);
                Glide.with(activity)
                        .load(responseList.user.profileImageUrlHttps)
                        .into(rowViewHolder.userImage);

                rowViewHolder.userName.setText(responseList.user.screenName);

                Date date = new Date(responseList.createdAt);
                DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(activity.getApplicationContext());
                rowViewHolder.feedDate.setText(dateFormat.format(date));

                rowViewHolder.feedRow.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        startActivity(position);
                    }
                });
            }
        }
    }

}

// setting Row Image.

private void setRowImage(RowViewHolder rowViewHolder, Tweet responseList) {
    if (responseList.entities.media != null) {
        MediaEntity mediaEntity = responseList.entities.media.get(0);
        if (mediaEntity.mediaUrlHttps != null) {
            int height = mediaEntity.sizes.medium.h;
            int width = mediaEntity.sizes.medium.w;

            int ratio = width / 143;
            int newHeight = (int) ((height / ratio) * density);

            Glide.with(activity)
                    .load(mediaEntity.mediaUrlHttps)
                    .override(width, newHeight)
                    .placeholder(R.color.colorAccent)
                    .into(rowViewHolder.feedImage);

        } else {
            rowViewHolder.feedImage.setImageDrawable(null);
        }
    } else {
        rowViewHolder.feedImage.setImageDrawable(null);
    }
}

// View condition to set header and row.

@Override
public int getItemViewType(int position) {
    if (isPositionHeader(position))
        return TYPE_HEADER;
    return TYPE_FEED;
}

private boolean isPositionHeader(int position) {
    return position == 0;
}

@Override
public int getItemCount() {
    return dataLists == null ? 0 : dataLists.size();
}

}

Answer

PirateApp picture PirateApp · Nov 8, 2015

I have a very similar Adapter in my app where I display feed from Facebook instead of Twitter, First of all, switch to Glide instead of Picasso since it has gives better caching and image loading with RecyclerView, check THIS. So coming to your problem now, call setHasStableIds(true) on your Adapter in the 1st step, override the getItemId method to return a suitable long from your Adapter, since you are using a feed, you can compute a hash say for the postId or some field that unique identifies each post within a feed. My feed has an optional image too which is why I have these 2 methods

public void setProfilePicture(String uri) {

        //As per the solution discussed here http://stackoverflow.com/questions/32706246/recyclerview-adapter-and-glide-same-image-every-4-5-rows
        if (uri != null) {
            Glide.with(mContext)
                    .load(uri)
                    .asBitmap()
                    .transform(new CropCircleTransform(mContext))
                    .into(mProfilePicture);
        } else {
            Glide.clear(mProfilePicture);
            mProfilePicture.setImageResource(R.drawable.com_facebook_profile_picture_blank_square);
        }
    }

    public void setPostPicture(String uri) {

        //As per the solution discussed here http://stackoverflow.com/questions/32706246/recyclerview-adapter-and-glide-same-image-every-4-5-rows
        if (uri != null) {
            Glide.with(mContext)
                    .load(uri)
                    .asBitmap()
                    .transform(new CropTransformation(mContext, mPostImageWidth, mPostImageHeight))
                    .into(mPostPicture);
        } else {
            Glide.clear(mPostPicture);
            mPostPicture.setImageDrawable(null);
        }
    }

and then all you gotta do is call these methods from your onBindViewHolder to set the Image. Post is my object that contains the details of a single post such as name, userid , image url for the person s picture and image url for the post picture which is optional. Let me know if you encounter any issues further.

@Override
    public void onBindViewHolder(ItemHolder holder, int position) {
        Post post = mResults.get(position);
        holder.setUserName(post.getUserName());
        holder.setUpdatedTime(post.getUpdatedTime());
        holder.setMessage(post.getMessage(), mState, position);
        holder.setPostPicture(post.getPicture());
        holder.setProfilePicture(post.getUserPicture());
        // Check for an expanded view, collapse if you find one

    }