How to make RecyclerView stops recycling defined positions?
Asked Answered
G

7

30

My problem is: I have a video streaming happening on one of the views inside the RecyclerView.

When the user scrolls, the view gets recycled and other cameras starts their own streaming on that recycled viewholder. This is bad for user interface since the streaming process takes some seconds to start.

How can I say to the RecyclerView: "Hey Recycler, please, do not recycle that exact position x and give that position ALWAYS the same viewholder you gave it the first time, instead of random one"?

Please someone help me =(

Grummet answered 13/2, 2017 at 16:44 Comment(2)
if you don't want recycling, maybe you can use ScrollViewBruner
The problem is that it is a list of Devices. A lot of devices don't have this problem, but some of those devices are Cameras. I want to start streaming by user press on those camera cards, but the view get recycled when scrolled of. That is the stuff I don't want.Grummet
C
28

In your getItemViewType(int position) method of adapter, assign unique values for each video, so it will always return same ViewHolder for same video as you wish.

  • return unique positive number as type for each video type (here i used the adapter position as unique key)
  • return negative numbers for any non-video items. (nothing special here, just to avoid conflicts with video items, we use negative numbers for non-video items)

I hope you get the idea. cheers :)

    @Override
    public int getItemViewType(int position) {
        // Just as an example, return 0 or 2 depending on position
        // Note that unlike in ListView adapters, types don't have to be   contiguous
        if(dataList.get(position).isVideo()){
            return position;

        }else{
            return -1;//indicates general type, if you have more types other than video, you can use -1,-2,-3 and so on.
        }
    }

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case -1:  View view1 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.general_item, parent, false);
                     return new GeneralViewHolder(view1);
             default:View view2 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.video_item, parent, false);
                     return new VideoViewHolder(view2);

         }
    }
Calli answered 3/4, 2017 at 17:54 Comment(3)
This a huge workaround that shows us that RecyclerView needs something official in its api to allow us to reach that behavior. But this actually WORKS and I am giving you my bounty points. Thanks Darish.Grummet
Beautiful solution.Weinrich
@Calli any idea about my view, i just dont want the first item to be recycled, I am even fine with not recycling the recyclerview at all either. here's my code : #42209668Beguin
S
25

Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.

From docs of ViewHolder#setIsRecyclable(boolean):

Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until setIsRecyclable() is later set to true.

This will cause only one ViewHolder to be created.

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ...
    @Override
    public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
        if (holder instanceof VideoViewHolder) {
            holder.setIsRecyclable(false);
        }
        super.onViewAttachedToWindow(holder);
    }

    @Override
    public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
        if (holder instanceof VideoViewHolder){
            holder.setIsRecyclable(true);
        }
        super.onViewDetachedFromWindow(holder);
    }
    ...
}
Slovenly answered 29/3, 2017 at 14:7 Comment(12)
I tried it. But the problem is: that view will not be recycled for none item, not even the original one... So, when the recycler reach the camera again it will create another view.Grummet
@DanielOliveira, from where do you perform viewholder.setIsRecyclable(false)?Slovenly
onBindViewHolder or onCreateViewHolder. This metod does exactly what it shoyuld: it will not recycle the view but another viewholder will be created, wich still doesn't resolve the problemGrummet
Man thanks for your effort, I tried your code but it stills gives different holders after the video card is shown. I am checking by the viewholder's.hascode() method they are different =(Grummet
it stills gives different holders I've checked, onCreateViewHolder is called only once.Slovenly
If I have 3 cameras on my list, i willl have 3 CameraHolders on the pool, and they will be given randomly to each camera. An easy way to test it is populating your list with 3 cameras and at least 10 other devices. Scroll the cameras off the screen and then scroll them back.Grummet
I've created a simple project at github with 3 unrecycable items. See logs, I can see precisely 3 ViewHolders being created.Slovenly
azizbekian can you take a look at my issue: #50356007Beguin
@MarissaNicholas, isn't the above-mentioned answer helpful?Slovenly
@Slovenly I tried that but it still recycles my views when i scroll. I set setIsRecyclable to false in both instances too. I am fine with not recycling the entire recyclerview if that means fixing the issue. Currently I just dont want the very first item to not recycler, since its holding self video and recycling it means interrupting broadcast. i have my entire code posted on my link, let me know if you have any ideaBeguin
@Slovenly can you re-add the github project on here? seems to be removedBeguin
I can confirm that setIsRecyclable(false) prevents the view from recycled and creates a new copy of the view when notifyItemChanged is called on the view.Reindeer
A
11

RecyclerView uses one view multiple times, when it contains the list which is not displaying on the screen at a time(means a list contain large amount of items which is not displaying on screen at same time you need to scroll up and down). When user scroll the list the offscreen items are reused to display the remaining list items which is called recycling.

To Stop recycling the items call this method in your onBindViewHolder method:

viewHolder.setIsRecyclable(false);

This statement stop the recycling the views.

To Start recycling the items call this method in your onBindViewHolder method:

viewHolder.setIsRecyclable(true);

I hope this will solve your problem. Thanks

Adamite answered 17/1, 2018 at 11:10 Comment(2)
Actually this does not solve. Instead of recycling, it will create a new one. My wish is to never recycler and never create a new one but always use the same.Grummet
@Daniel it is worked and solved my issue. Thanks for your opinion.Adamite
P
3

Your problem comes from the viewholder itself. Viewholders keep reference to views, while the adapter don't. The adapter keeps the data collection only. So, add a field to the viewholder to keep a reference of the data element you used to populate the view in the viewholder. In other words:

public class SomeViewHolder extends RecyclerView.ViewHolder{

    private View view;
    private Data data;

    public SomeViewHolder(View itemView) {
        super(itemView);
        view = itemView;
    }

    public void bindData(Data data){
        view.setData(data);
        this.data = data;
    }

    public void setData(Data data){
       this.data = data;
    }


    public Data getData(){
        return data;
    }

    public View getView(){
        return view;
    }
}

Now, the viewholder know which element of the adapter is using. Therefore, when overriding the binding method in the adapter, you can check if the holder has already bonded with some data, and, if the data contains video, you can avoid the binding and forcefully set an already loaded view.

@Override
public void onBindViewHolder(SomeViewHolder holder, int position) {

    //videoViewData is a data field you have to put into the adapter.
    //videoView is a view field you have to put into the adapter.

    if(adapterData.get(position).equals(videoViewData)){
        holder.setView(videoView);
        holder.setData(adapterData.get(position));
    }else{
        holder.bindData(adapterData.get(position));
        if(adapterData.get(position).isVideo()){
            videoViewData = adapterData.get(position);
            videoView = holder.getView(); 
        }
    }

}

Finally, you'll have to override the onViewRecycled method in the adapter, so, when a view containing a video gets recycled, you can get the view and put it somewhere else.

public void onViewRecycled(SomeViewHolder holder){
    if(holder.getData().isVideo()){
        videoViewData = holder.getData().
        videoView = holder.getView();
        videoView.pauseVideo();
    }
}

keep in mind, this can cause some serious leaks if you don't manage the stored view. Also, you have to define methods for telling when your data is video, and a properly defined equals method.

Persevere answered 1/4, 2017 at 5:28 Comment(1)
Interesting approach, but there is still a problem. How to "remove" the videoView from one holder and add it into another ?Grummet
G
0

Best way to handle item not to recycle in recyclerview this answer will resolve your problem.

Not to recycle item

Gascon answered 9/4, 2019 at 5:17 Comment(0)
L
-2

Try using this for that particular position:

 holder.setIsRecyclable(false);

Hope this may help.

Lamont answered 3/4, 2017 at 13:15 Comment(0)
F
-9

If You are using query, you can use

query.limit(//no of items you want to show in your RecyclerView)  

give it a try.

or Plese post your QueryCode

Fadiman answered 13/2, 2017 at 16:52 Comment(4)
Query? What do you mean?Grummet
How are you putting your data in recycler view? Please post your codeFadiman
Its a simple ArrayList of itens, each item has a boolean indicating if it is a videostreaming or a simple photo.Grummet
layoutManager= new LinearLayoutManager(getApplicationContext(),//no of items you want to show); then in your adapter return // no of items you want to show .size().Fadiman

© 2022 - 2024 — McMap. All rights reserved.