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 =(

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Daniel Oliveira
  • 8,053
  • 11
  • 40
  • 76
  • if you don't want recycling, maybe you can use `ScrollView` – Linh Mar 29 '17 at 03:39
  • 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. – Daniel Oliveira Mar 29 '17 at 13:22

7 Answers7

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);

         }
    }
Darish
  • 11,032
  • 5
  • 50
  • 70
  • 1
    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. – Daniel Oliveira Apr 03 '17 at 22:56
  • 1
    Beautiful solution. – Vicky Apr 16 '18 at 12:34
  • @Darish 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 : https://stackoverflow.com/questions/42209168/how-to-make-recyclerview-stops-recycling-defined-positions/43095916?noredirect=1#comment87766110_43095916 –  May 16 '18 at 15:41
24

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);
    }
    ...
}
azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • 3
    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. – Daniel Oliveira Mar 29 '17 at 14:54
  • @DanielOliveira, from where do you perform `viewholder.setIsRecyclable(false)`? – azizbekian Mar 29 '17 at 14:58
  • 2
    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 problem – Daniel Oliveira Mar 29 '17 at 15:05
  • 2
    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 =( – Daniel Oliveira Mar 29 '17 at 16:05
  • `it stills gives different holders` I've checked, `onCreateViewHolder` is called only once. – azizbekian Mar 29 '17 at 16:07
  • 1
    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. – Daniel Oliveira Mar 29 '17 at 18:13
  • I've created a simple project at [github](https://github.com/azizbekian/RecyclerTest) with 3 unrecycable items. See logs, I can see precisely 3 `ViewHolder`s being created. – azizbekian Mar 29 '17 at 19:59
  • azizbekian can you take a look at my issue: https://stackoverflow.com/questions/50355507/how-do-i-not-recycle-only-the-first-2-views-of-my-recyclerview/50355940#50355940 –  May 16 '18 at 15:28
  • @MarissaNicholas, isn't the above-mentioned answer helpful? – azizbekian May 16 '18 at 15:36
  • @azizbekian 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 idea –  May 16 '18 at 15:43
  • @azizbekian can you re-add the github project on here? seems to be removed –  May 18 '18 at 10:44
  • 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. – Ray Li Feb 22 '19 at 18:21
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

Aman Goyal
  • 409
  • 5
  • 8
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.

Fco P.
  • 2,486
  • 17
  • 20
0

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

Not to recycle item

Ness Tyagi
  • 2,008
  • 24
  • 18
-2

Try using this for that particular position:

 holder.setIsRecyclable(false);

Hope this may help.

-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

Saurabh
  • 136
  • 1
  • 1
  • 11