9
  • 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();
}

}

Abhishek
  • 2,350
  • 2
  • 21
  • 35
  • instead of making the visibility of row gone why dont you remove the dataLists entries containing "RT" in the constructor and then set the adapter – Amit Kumar Nov 07 '15 at 06:09
  • ya i have tried to check that condition in fragment before passing it to constructor UnsupportedOperationException – Abhishek Nov 07 '15 at 07:01
  • can you show me the condition? – Amit Kumar Nov 07 '15 at 07:06
  • try recentData.addAll(recentData.data.tweets) inplaceof List recentData = recentData.data.tweets – Amit Kumar Nov 07 '15 at 07:13
  • http://stackoverflow.com/questions/2965747/why-i-get-unsupportedoperationexception-when-trying-to-remove-from-the-list – Amit Kumar Nov 07 '15 at 07:14
  • Have you found solution ? If not then can you please try this....[link](http://stackoverflow.com/a/32766674/5059946) and let me know what is result ? – shreyash mashru Nov 18 '15 at 04:58
  • no i have not found any solutions. i have edit my issues. sry for late corrections. – Abhishek Nov 18 '15 at 05:58
  • you can try this and check `rowViewHolder.setIsRecyclable(false);` inside `onBindViewHolder()` – Satyen Udeshi Nov 25 '15 at 04:25
  • if holder is an instance of header, then add the header data else, directly add the row data instead of checking if holder is an instance of the row, i dont have your complete code but i'd be willing to run it myself and test it if you want – PirateApp Nov 25 '15 at 04:48
  • @PirateApp , I want to set both and both instance depends on each other and am getting those at run time plus am setting those both at same time bez row depends to header. – Abhishek Nov 25 '15 at 06:34

4 Answers4

3

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

    }
PirateApp
  • 5,433
  • 4
  • 57
  • 90
  • setHasStableIds(true) was already done. well i have tried using Glide library, row is still shuffling. – Abhishek Nov 17 '15 at 10:52
  • @abhishek how are you implementing getItemID? – Tunji_D Nov 24 '15 at 19:58
  • I like your code, I have a recycler adapter which I wanted to implement the expandable view option, in the expandable option I wanted to put a horizontal scrollview and it seems u have implemented some form of expandable adapter, coulf your please share your entire code or atleast show me how i can implement that? – life evader Nov 24 '15 at 22:32
  • 1
    @lifeevader I have answer one ques where cardview is expanding in recycler view. http://stackoverflow.com/questions/31911684/android-how-to-expand-and-collapse-cardview-size-in-recyclerview/31911856#31911856 .. You can set your horizontal recyclerView's visibility there. let me know if it does not worked out. – Abhishek Nov 25 '15 at 06:44
  • @Tunji_D, I am not implementing getItemID bez i dont need it. – Abhishek Nov 25 '15 at 06:54
  • @abhishek which means there is no way for the adapter to know your item in particular , check line 5287 https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java I believe your feed has a set of posts with something unique for each post, you should override the getItemId to return that unique value in long – PirateApp Nov 25 '15 at 07:00
  • @abhishek also check 3028 https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java where hasStableIds is checked and getItemId is called, since you did not override it, your adapter is messing up I believe – PirateApp Nov 25 '15 at 07:03
  • @PirateApp I mean i dont need that to implement as i am using getItemViewType it also return position and i am passing array of object with all field in String data type. – Abhishek Nov 25 '15 at 07:19
  • getItemViewType has nothing to do with getItemId, getItemViewType is there to tell what type of view you are having at a given position, getItemId tells what is the aspect that unique identifies the item at this position compared to the other items. Most implementations that return a position from getItemId are good for demonstration purposes, if your app is displaying data from a database or a json feed you should consider using some primary key or timestamp or some column that will identify each item uniquely, you can compute a hash value based on the values of the other columns if you want – PirateApp Nov 25 '15 at 10:16
  • If you have setHasStableIds to true and you don't implement geitemID, your RecyclerView WILL duplicate items. Like @PirateApp said, you must identify each item in the list uniquely with some hash. This is also important for the RecyclerView to animate additions, deletions, and rearrangement to the list. – Tunji_D Nov 25 '15 at 15:39
2

The issue you have is that you are not implementing getItemID, or you are not implementing it properly.

If you set setHasStableIds to true and you do not implement getItemId to return a unique long for each item in the list, the RecyclerView WILL duplicate items. This is because the default implementation of getItemID returns -1 for all items in the list, therefore, when onBindViewHolder is called after a view is recycled, it's going to return a cached view of the last thing that ViewHolder displayed.

An easy way for you to implement this is just to return the hashcode for the object in the list bound to that ViewHolder.

@Override
public long getItemId(int position) {     
    Object listItem = listItems.get(position);
    return listItem.hashCode();  
}

Obviously, this means for that particular object, you must have overriden it's equals() and hashCode() methods to be unique for each object. A good idea is a String showing when it was created if from a database concatenated with the object's name.

Tunji_D
  • 3,677
  • 3
  • 27
  • 35
2

If you see the source code of the RecyclerView over HERE navigate to line 3028 which contains these lines

/**
 * Returns a unique key to be used while handling change animations.
 * It might be child's position or stable id depending on the adapter type.
 */
long getChangedHolderKey(ViewHolder holder) {
    return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
}

and at line 5279 in the source file that contains this

/**
     * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
     * would return false this method should return {@link #NO_ID}. The default implementation
     * of this method returns {@link #NO_ID}.
     *
     * @param position Adapter position to query
     * @return the stable ID of the item at position
     */
    public long getItemId(int position) {
        return NO_ID;
    }

it clearly indicates that overriding setHasStableIds needs to be followed by overriding getItemId method. To have each item inside your RecyclerView display the correct item when you scroll up and down getItemId cannot return a position or worse be left off. Make sure you return a unique long value that identifies each row in your data set.

What could be this unique value?

  1. A unique timestamp of when your twitter post was made if you have it?
  2. A computed unique long value such as hash over one or more items from your data set that would yield a unique value.

This is important for both animations and displaying the proper row at the correct position. Hope that helps fix your issue

PirateApp
  • 5,433
  • 4
  • 57
  • 90
0

Override these 2 methods in your recyclerview adapter and change the code of getItemViewType as below.

 @Override
        public long getItemId(int position) {
            return position;
        }

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

I also had the same problem. Do this. This will definitely solves your problem.

Fathima km
  • 2,539
  • 3
  • 18
  • 26