I have a RecyclerView.Adapter with hasStableIds(true); and a LinearLayoutManager with reverseLayout(true);
I insert a new item to the object list in the adapter then call NotifyItemInserted(0). The list is reversed and loads from bottom to top with the new item always at the bottom.
The problem is when calling NotifyItemInserted all of the ViewHolders are "Refreshed" / "Reloaded" / "Recreated" - The viewholders are a bit complex and do not want them all to be recreated each time.
When I debug the solution, OnBindViewHolder starts from position 0 (Which is correct) then goes up to position 10 or 11 depending on how many items there are on screen, recreating them all.
I have tried many different settings on the adapter, layoutmanager and the recyclerview itself which none of them work.
I see there is a payload which can be passed in NotifyItemChanged but not in NotifyItemInserted. Maybe with the payload I can check whether the item is already on screen and don't recreate it again, I don't know what to do.
I am using Xamarin.Android but that should not be a problem here.
Removing hasStableIds(true) should solve your problem.
Reason:
When you set hasStableIds to true, the Adapter assumes that every item has a unique id, and it uses the values returned from the getItemId(int position) method to animate data changes for you.
That's an easy way to animate changes without any effort (As long as the getItemId(int position) really returns a unique id per item, otherwise you'll crash).
But in your case you want to animate changes by yourself and control which items will be re-rendered and which will not.
Related
I read too many articles about this. I Tried all type of permutation & combination of recyclerview method such as setHasFixedSize(true) ,setNestedScrollingEnabled(false), setItemViewCacheSize(20) etc. But my prediction is the below statement is ture:
The main reason Jank occurs the first time because on the first time it is loading the values onto memory dynamically. while once it is down it already has a few elements pre loaded
When I run my app on android the first time I can see a first scroll is lagging(exactly 5-6 items).Once that first scroll is done the list view is really really fast, I mean faster than anything else.
If I close my app without killing it from background and open it again, It won't lag.
if I "kill" my app and run it again, I get the lag for first 5-6 items.
MY QUESTIONS :
Is there any way to load the values onto memory dynamically before user interaction, Like showing a splash screen and load the layout in background thread? I have tried splash screen but this question is how to load the layout onto memory.
How is the scrolling of youTube,Twitter,Instagram is smooth? How are they load their layout first time onto memory?
Note: Main container is ConstraintLayout with custom background & my hierarchy is totally flat, but 3 buttons have drawable icons and custom background , one material shapable imageview, one slider with custom thumb and custom progress drawable and a frameLayout
Make sure to override:
getItemViewType(…) from docs:
Return the view type of the item at position for the purposes of view
recycling.
The default implementation of this method returns 0, making the
assumption of a single view type for the adapter. Unlike ListView
adapters, types need not be contiguous. Consider using id resources to
uniquely identify item view types.
If you have different view types, return an int that identifies said view types, otherwise no need to override.
getItemId(…) from docs:
Return the stable ID for the item at position. If hasStableIds would
return false this method should return NO_ID. The default
implementation of this method returns NO_ID.
That means the function must return a unique id for every item. Use the id of your objects if there's any, otherwise try with the hashCode:
#Override
public long getItemId(int position) {
return itemList.get(position).hashCode();
}
If implemented properly, use setHasStableIds(true). From docs:
Indicates whether each item in the data set can be represented with a
unique identifier of type java.lang.Long.
getItemCount(…) from docs:
Returns the total number of items in the data set held by the adapter.
Simply the number of items in the list.
#Override
public int getItemCount() {
return itemList.size();
}
I am currently learning android and first time adding swipe to delete functionality to the RecyclerView. I came across the following theory regarding to the swiping functionality but i am unable to understand the reason discussed below as to why getAdapterPosition will return RecyclerView.NO_POSITION.
When adapter contents change (and you call notify***) RecyclerView requests a new layout.
From that moment, until layout system decides to calculate a new layout (<16 ms), the layout
position and adapter position may not match because layout has not reflected adapter changes yet.
If you are calling notifyDataSetChanged, it invalidates everything, RecyclerView does not know
that ViewHolder's adapter position until next layout is calculated.
In that case, getAdapterPosition will return RecyclerView.NO_POSITION (-1)
If you've called notifyItemInserted(0), the getAdapterPosition of ViewHolder which was
previously at position 0 will start returning 1 immediately.
As long as you are dispatching granular notify events, you are always in good state (we know
adapter position even though new layout is not calculated yet).
Can someone please explain the above reason and the solution in a simple way.
-1 means the scroll could not be computed or the actual position is not valid (in between).
The solution is ignore it
if (getAdapterPosition() == -1) return;
I have used the RececlerView with the ViewHolder pattern for a while now.
Im am implementing a custom Adapter.
Im am not searching for a specific bug help in my code.
I was just wondering, if it's normal, that the onBindViewHolder method is called multiple times (for the same item) while scrolling to the end of the list and scrolling back up. In this case onBindViewHolder is called again for item 0 and 1 (the list contains 7 items in total)
Is there any possibility for this method to get called AGAIN without notifying that the datasat has changed?
Im a bit confused.
Kind Regards,
Palm
Yes it is perfectly normal for a RecyclerView to call onBindViewHolder() multiple times.
A RecyclerView only creates minimum number of Views needed to fill the screen. And it works by reusing the old/created Views. So that when you are scrolling down the View that hid during the scrolling to the top is removed and brought next to the last visible View and added there. But since the View is currently bound with old data onBindViewHolder() is called again to ensure that the View is bound with only the correct data before it is rendered.
Similarly you'll notice that onCreateViewHolder() is only called the exact minimum number of Views it needs.
For a better understanding of how the RecyclerView works I suggest you read up on Recycler, LayoutManager and Recycler.Adapter the three core parts of a RecyclerView.
I have a dialog with RecyclerView of IDs in it. While I was choosing an id it changes its background. I did it by replacing adapter where I am putting newly selected position. And in my adapter item layout was chosen by its position where if equals to selected, it has another layout.
So after resetting a new adapter to recycler it losses current scroll position. I tried to use scrscrollToPosition, but it still not perfect, due to the situation that scrolled position is always on a beginning of RecyclerView.
So as I understand the problem I need to remember reference or some other scrolling data for setting it on the new adapter.
So If someone knew relevant RecyclerView methods or good practice for this problem, it would be nice =)
I don't think that here I need some code, cause it more practice question, but if someone need it I would add it.
Update: Meantime I found this method for getting offset of RecyclerView but still looking how to use this data in new adapter.
int offset = recyclerViewDialog.computeHorizontalScrollOffset();
This is going to be a very straight-forward question.
When you perform a
adapter.notifyDataSetChanged(), ListView refreshes all it's content, by reloading every single row.
I want to avoid this relayout, so I've read this answer(Android - what is the meaning of StableIDs?):
Stable IDs allow the ListView to optimize for the case when items remain the same between
notifyDataSetChanged calls. The IDs it refers to are the ones returned
from getItemId.
Without it, the ListView has to recreate all Views since it can't know
if the item IDs are the same between data changes (e.g. if the ID is
just the index in the data, it has to recreate everything). With it,
it can refrain from recreating Views that kept their item IDs.
So I overrode that method and returned true, then I managed to get a unique ID for every row. However, when I trigger adapter.notifyDataSetChanged the listview recalculates everything.
The thing, is that I want to avoid this all recalculation, because the existing rows are not going to change, just several news are going to be added in the bottom (think about a pagination).
Is there any way to avoid full relayout of the ListView?
Thank you.