I have a list with 13 items (although items may be added or removed), positions 0-12. When the fragment containing the RecyclerView is first shown, only positions 0 through 7 are visible to the user (position 7 being only half visible). In my adapter I Log
every time a view holder is binded/bound (idk if grammar applies here) and record its position.
Adapter
@Override public void onBindViewHolder(final ViewHolder holder, final int position) { Log.d(TAG, "onBindViewHolder() position: " + position); ... }
From my Log
I see that positions 0-7 are bound:
I have a selectAll()
method that gets each ViewHolder
by adapter position. If the returned holder
is NOT null
I use the returned holder
to update the view to show it's selected. If the returned holder IS null
I call selectOnBind()
a method that flags the view at that position update to show it's selected when it's binded rather than in real time since it's not currently shown:
public void selectAll() { for (int i = 0; i < numberOfItemsInList; i++) { MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(i); Log.d(TAG, "holder at position " + i + " is " + holder); if (holder != null) { select(holder); } else { selectOnBind(i); } } }
In this method I Log
the holder
along with its position:
So up to this point everything seems normal. We have positions 0-7 showing, and according to the Log
these are the positions bound. When I hit selectAll()
without changing the visible views (scrolling) I see that positions 0-7 are defined and 8-12 are null
. So far so good.
Here's where it gets interesting. If after calling selectAll()
I scroll further down the list positions 8 and 9 do not show they are selected.
When checking the Log
I see that it's because they are never bound even though they were reported to be null
:
Even more confusing is that this does not happen every time. If I first launch the app and test this it may work. But it seems to happen without fail afterwards. I'm guessing it has something to do with the views being recycled, but even so wouldn't they have to be bound?
EDIT (6-29-16)
After an AndroidStudio update I cannot seem to reproduce the bug. It works as I expected it to, binding the null views. If this problem should resurface, I will return to this post.
In your case views for positions 8 and 9 are not being recycled, they are being detached from the window and will be attached again. And for these detached view onBindViewHolder
is not called, only onViewAttachedToWindow
is called. If you override these function in your adapter, you can see what I am talking.
@Override public void onViewRecycled(ViewHolder vh){ Log.wtf(TAG,"onViewRecycled "+vh); } @Override public void onViewDetachedFromWindow(ViewHolder viewHolder){ Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder); }
Now in order to solve your problem you need to keep track of the views which were supposed to recycled but get detached and then do your section process on
@Override public void onViewAttachedToWindow(ViewHolder viewHolder){ Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder); }
This is happening because:
getChildAt
will not work and will return null for that position) onBind
will not be called) Calling recyclerView.setItemViewCacheSize(0)
will fix this "problem".
Because the default value is 2 (private static final int DEFAULT_CACHE_SIZE = 2;
in RecyclerView.Recycler
), you'll always get 2 views that will not call onBind
but that aren't added to the recycler
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With