50

I want to update the RecyclerView while it's displaying data, in my case, I show images with or without labels.

Defaultly I set the visibility of the label when I create the view holder and that's fine, but I want the user to change the labels visibility through the menu while the RecyclerView is shown, so I want to manually update the visibility for all existing views in the RecyclerView.

Can I somehow get all existing Views? I need all, not only the visible ones, I don't want that a later recycled View is not updated...

prom85
  • 16,896
  • 17
  • 122
  • 242
  • Do you have any parameters to know if label is visible or not in the onBindViewHolder? You should check for this parameter. Update the parameter in the menu and set adapter.notifyDataSetChanged – Chol Jan 12 '16 at 11:20
  • I'm reading from the preferences, so I want to do this ONCE and not always... I could do it with an internal boolean in the adapter as well, didn't think about that actually... – prom85 Jan 12 '16 at 11:22
  • Is this parameter is the same for all the item? or can be different depending on item? – Chol Jan 12 '16 at 11:23
  • Same for all... That's why I would prefer iterating over all existing views... – prom85 Jan 12 '16 at 11:25
  • You can pass this value to the adpater constructor, and create a method in adpater to change this value – Chol Jan 12 '16 at 11:27
  • That's what I do. But in `onBindView` I have to ALWAYS set the visibility whereas I would prefer to do that only once when I create a view and only when the variable changed... – prom85 Jan 12 '16 at 11:29
  • It's not a good idea to change Views under recycler view manually change models and notify changes instead. – dilix Jan 12 '16 at 11:39

5 Answers5

87

First you need to get all the views' indices that are shown, and then you need to go over each, and use the viewHolder of each view:

final int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
    ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(i);
    ...
    }

EDIT: seems it doesn't always return all ViewHolders you might want to handle. This seems like a more stable solution:

for (int childCount = recyclerView.getChildCount(), i = 0; i < childCount; ++i) {
   final ViewHolder holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
   ...
   }

Note: on some cases, you might want to set the number of Views being cached to be large enough so that you will always get the same views recycled, instead of new ones. For this, you can use something like that:

fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
    for (i in 0..maxViewTypeId)
        recycledViewPool.setMaxRecycledViews(i, maxPoolSize)
}

Example usage:

recyclerView.setMaxViewPoolSize(MAX_TYPE_ITEM, Int.MAX_VALUE)
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 10
    This is the correct answer to the question "get all existing views/viewholders", and exactly what I needed. Thanks a lot! – Albert Vila Calvo May 13 '16 at 14:06
  • This answer would be more helpful if you illustrated how you obtained reference to the layoutManager variable. – Casey Perkins Oct 05 '16 at 14:28
  • 1
    @JasonTyler In order to use a RecyclerView, you always create a LayoutManager instance, so you have it before. Here's one way to create it, to work like a vertical list (but again, you need to choose which to use) : LayoutManager layoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false) – android developer Oct 06 '16 at 05:45
  • 12
    This is not correct either, there are bound view holders that are not yet visible: after loading at startup usually there are 2 or 3 items bound over the last visible position. – Davideas Dec 18 '16 at 17:45
  • @Davidea Can you please tell how to do it? – android developer Dec 20 '16 at 08:59
  • 2
    @androiddeveloper, I just experimented watching the logs i put, that it binds more items that are not visible, indeed I found no way to retrieve them, I ended up to cache in a set or list those `ViewHolders` in `onBindViewHolder()` and to uncache them in `onViewRecycled()` – Davideas Dec 20 '16 at 15:51
  • I added a new answer for a possible better solution. – Davideas Dec 26 '16 at 09:19
  • @Davidea this is what you are looking for I guess: http://stackoverflow.com/a/41802632/2649104 – Srujan Barai Jan 23 '17 at 09:14
  • 1
    I can confirm @Davidea finding is correct. There are more cached view holder, than (lastVisibleItemPosition - firstVisibleItemPosition + 1) – Cheok Yan Cheng Jun 08 '17 at 11:23
  • In the edit you are using `recyclerView` and `mRecyclerView` variables. Is this a mistake or are they somehow different? – rozina Apr 05 '18 at 08:44
  • It's the same solution. Its the same line of code: `final ViewHolder holder = recyclerView.getChildViewHolder(mRecyclerView.getChildAt(i))` Is it a mistake or are they different? If they are, what is one and what is the other? – rozina Apr 05 '18 at 13:00
  • similer question, please help me https://stackoverflow.com/questions/55516858/unable-to-show-imageview-in-recyclerview – Dan Apr 07 '19 at 22:24
  • This will not work if you have more than one viewType. Some holders (right after the last visible one) can be in cache, not returned by these methods. What fixed it for me is setting `recycler.setItemViewCacheSize(0);` and I know it's stupid workaround. – JoKr Apr 12 '19 at 11:06
  • @Gudin What will not work? What will happen? Have you tried to set the opposite, meaning have a huge limit for the pool of views? Meaning `recyclerView.recycledViewPool.setMaxRecycledViews(viewType,Int.MAX_VALUE)` for each of the viewTypes ? – android developer Jul 07 '19 at 09:58
  • you should use `adapter.getItemCount()` instead of `recyclerView.getChildCount()` because it is returning wrong count. – ATES May 25 '20 at 09:32
  • @ATES Wrong count of what? We need to change the children. How could we reach the x-child if we go over more than the child count? – android developer May 25 '20 at 15:09
  • I mean getChildCount() returning only visible childs, if you use nested scroll view on top and all of items are visible, (like me) you should use adapter.getItemCount() for reach them. – ATES May 25 '20 at 15:15
  • @ATES I think it's better to cache the ViewHolders then, as suggested by others. No point in going over the entire list (which could be huge) just for that. – android developer May 25 '20 at 19:09
  • None of these worked for me, there were still ViewHolders that I didn't get, only the solution from @Davideas worked for me – Christian C Sep 22 '21 at 13:50
  • @CristiCristi Can you please put a link? – android developer Sep 23 '21 at 07:47
  • @androiddeveloper [Sure](https://stackoverflow.com/a/41328933/6253148) – Christian C Sep 23 '21 at 10:14
  • @CristiCristi So it's as I wrote later "I think it's better to cache the ViewHolders then, as suggested by others" . – android developer Sep 23 '21 at 11:01
  • I find it amazing that by doing an object that gets "created", then binded "start", then recycled "stop", only for it to then be completely unable to be accessed on its destruction phase. Is like they said "All library leaks are welcomed in here." and "We know recycling is to make things less expensive but don't ever use the creation phase lol" – Delark Dec 26 '21 at 19:15
34
  • Updating the whole adapter list size with notifyDataSetChanged() is not convenient.
  • Getting all the current RecyclerView childs (ie. all visible items) is not enough: there are bound view holders that are not visible: after loading at startup usually there are 2 or 3 items bound over the last visible position (depending by your LayoutManager).

I just experimented watching the logs i put, that RV binds more items that are not visible, indeed I found no way to retrieve them. I ended up to cache in a Set or List those ViewHolders in onBindViewHolder() and to uncache them in onViewRecycled():

private Set<AbstractViewHolder> mBoundViewHolders = new HashSet<>();
private int mVisibility = View.VISIBILE;

// So you can have access from the Activity/Fragment
public Set<AbstractViewHolder> getAllBoundViewHolders() {
    return Collections.unmodifiableSet(mBoundViewHolders);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
    // Binding code
    ...
    holder.view.setVisibility(mVisibility);

    // Caching VH
    mBoundViewHolders.add((AbstractViewHolder) holder);
}

@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
    // Uncaching VH
    mBoundViewHolders.remove(holder);
}

// Your method becomes
public void updateVisibility(int visibility) {
    mVisibility = visibility;
    for (AbstractViewHolder holder : mBoundViewHolders) {
        holder.updateVisibility(visibility);
    }
}

Implement your interface or abstract ViewHolder so you can call your method. In this way you have access to your ViewHolder content.


This is what I do in my FlexibleAdapter to unselect the items without invoking the notifyItemRangeChanged(). And if user has animations, all are executed, plus the background changes too when I invoke my method toggleActivation().

If you know a better way to retrieve all bound VH, let me know.

Pang
  • 9,564
  • 146
  • 81
  • 122
Davideas
  • 3,226
  • 2
  • 33
  • 51
  • What would happen if instead of add&remove into the set, I'd just store all of the ViewHolders inside the onCreateViewHolder ? – android developer Dec 27 '16 at 17:32
  • @androiddeveloper In my library it cannot work because the VH has not yet been created. But you can try and see what happens in your adapter. – Davideas Dec 27 '16 at 22:43
  • @Davidea I didnt you this answer before! Good workaround man! – Srujan Barai Jan 25 '17 at 02:51
  • 2
    Is it better to store VH, using `WeakReference` ? – Cheok Yan Cheng Jun 08 '17 at 11:28
  • May I recommend using this data structure to store cached VH - `Set mBoundViewHolders = Collections.newSetFromMap(new java.util.WeakHashMap());` – Cheok Yan Cheng Jun 08 '17 at 15:56
  • @CheokYanCheng, I would not suggest to use `WeakReference` in this case, since we might need to access more often to the same ViewHolder... that is currently displayed on screen. – Davideas Jul 11 '17 at 15:26
  • This is excellent. I had to toggle "select all" - "select none" from recycler. There are items not yet drawn on screen so caching solved my problem. Notify data changed event also works but is is not good for my case as I'm not adding/removing items, just toggling check box and data change event causes redraw which flicks the screen.. – Brlja Nov 18 '18 at 13:21
  • Great solution @Davideas. I'm struggling, though, to understand if we need to clear the set ourselves when the activity gets stopped or destroyed to prevent leaks. What do you think? I've tried to clear the set in onStop but when the activity gets resumed the set doesn't get filled again with the drawn views – Oz Shabat Jul 30 '20 at 09:38
16

In adapter class:

Boolean isVisible = false;

public CustomAdapter(boolean isVisible) {
    this.isVisible= isVisible;
}
    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
     ...

         if (isVisible){
           //setVisibility(View.VISIBLE)
         }else{
         //setVisibility(View.INVISIBLE
         }
    }

public void updateVisibility(boolean newValue){
 isVisible= newValue;
}

In Activity where you want to update the value where the adapter is instantiated:

adapter.updateVisibility(false);
adapter.notifydataSetChanged();
Yuca
  • 6,010
  • 3
  • 22
  • 42
Chol
  • 2,097
  • 2
  • 16
  • 26
  • 1
    In `updateVisibility` you have to add `notifyDataSetChanged()` but then this works and that's my current solution... Still, for me, it seems like not perfect, the user changes the visibility very rarely whereas he scrolls nearly always, so setting visibility all the time is not the most effective solution for me... – prom85 Jan 12 '16 at 11:34
  • @prom85, inside the View class there are some checks that visibility has been changed, so if you call `setVisibility` with the same value it's not an issue. – dilix Jan 12 '16 at 11:38
  • My tests show that prom85 is right: I _do_ have to call `notifyDataSetChanged()` to force the RecyclerView to redraw the changes in items that are already displayed but need to be changed. – SMBiggs Oct 13 '16 at 05:59
  • Very grateful to you – Shamsul Arefin Jan 10 '18 at 12:52
  • This solution is the best solution in this page, because it is the only one that updates visibility for all recyclerview items - includes those who were not scrolled yet! – M. Marmor Jul 12 '18 at 20:25
11

The all existing views are the ones visible, plus some views cached by RecyclerView. You may access all visible Views like that:

View child;
for (int i = 0; i < mRecycler.getChildCount(); i++) {
    child = mRecycler.getChildAt(i);
    //In case you need to access ViewHolder:
    mRecycler.getChildViewHolder(child);
}

Even if you access cached Views they're not bound to data yet, so you won't know if label should be visible or not. Simply set label visibility of remaining Views in onBindViewHolder.

The rest of the Views simply doesn't exist yet, so you can't change anything about them, even if it's the same label visibility state for all of them.

InTwoMinds
  • 655
  • 4
  • 9
  • 5
    This does not work, not all cached views seem to returned if I do it like that... – prom85 Jan 12 '16 at 11:41
  • 1
    Where did i wrote that this gives you access to **cached** views? There's no point in accessing them anyway. Simply modify **visible** views like I said above, the rest should be modified in `onBindViewHolder` when `RecyclerView` needs them. – InTwoMinds Jan 12 '16 at 11:55
  • 2
    I said: "**all existing** are the ones **visible** plus some views **cached**". And then: "You may access all **visible** views like that:" Not a word about method to access **cached** views. You do need to modify visible views if you want the visibility change to be applied before view is scrolled offscreen and rebound in 'onBindViewHolder'. The problem is not the problem anymore. The problem is your attitude about the problem. – InTwoMinds Jan 12 '16 at 12:11
0

All of the above solutions gives only the visible holders but in reality there are 2-3 other holders that are bound but not visible. To get those as well, the simplest and the accurate way I can think of to get all the holders is:

int lastBoundHolderPosition = -1;
int lastDetachedHolderPosition = -1;

public void onBindViewHolder(final MyViewHolder holder, int position) {
    lastBoundHolderPosition = position;
    //Your implementation
}
public void onViewDetachedFromWindow(MyViewHolder holder){
    lastDetachedHolderPosition = holder.holder.getAdapterPosition();
}

When you want all the adapters visible (plus Bound but not visible):

if(lastDetachedHolderPosition == -1){
    for(pos = 0; pos <= lastDetachedHolderPosition; pos++){
        ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
    }
}else{
    if(lastBoundHolderPosition > lastDetachedHolderPosition){ //Scroll down
        //get all the bound holders
        for(int pos = lastDetachedHolderPosition + 1; pos <= lastBoundHolderPosition; pos++){
            ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
        }
    }else{ //Scroll up
        //get all the bound holders
        for(int pos = lastBoundHolderPosition ; pos <= lastDetachedHolderPosition-1; pos++){
            ViewHolder holder = (ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(pos);
        }

    }
}
Srujan Barai
  • 2,295
  • 4
  • 29
  • 52
  • What about getting the viewholders, when not scrolling ? For example, upon button clicked ? – android developer Jan 23 '17 at 13:46
  • hmmm... Another similar approach would be to has a Set of ViewHolders, and this will also avoid calling "findViewHolderForAdapterPosition" . Can be shorter and might be a tiny bit more effective. No ? – android developer Jan 23 '17 at 23:32
  • We want only the Current bound VH, the only way is to monitor when they are recycled(=unbound), which is the solution I proposed! `onViewDetachedFromWindow()` is called when VH is not visible but it is still bound, so it is not precise enough. – Davideas Jan 24 '17 at 14:32
  • First condition doesn't make sense. You check if `lastDetachedHolderPosition==-1` , and if so, go from 0 to it, meaning from 0 to -1 . It means the loop will never do anything. – android developer Jan 29 '18 at 12:45