How to keep Recyclerview items at same position? - android

I have the following issue. I populate a Recyclerview from my Fragment class. So far everything works out. However when I test my app and scroll up and down the populated recycler list the contents of each item change a.k.a. they get recycled...
How can I save each item's position and restore its content to the same position after scrolling?
Any suggestions?

Doing this holder.setIsRecyclable(false); will transform your RecyclerView into a ListView
Instead do this
Just override this two methods inside your RecyclerAdapter
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}

Good Question, this is your answer holder.setIsRecyclable(false).
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.product_recycle_buyer_list_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
holder.setIsRecyclable(false);
return holder;
}

setIsRecyclable(false) is bad solution, as it will create more and more views as you scroll, which ruins the whole point of using RecyclerView. Not only it takes more CPU, but the more you scroll and see new items, the more memory it will use. This is even worse, if you display bitmaps, as bitmaps tend to take huge amount of memory.
What you are supposed to do instead, is to implement onBindViewHolder to bind the view to the data that it's supposed to have. Also use cache in case of using bitmaps.
You can look at a sample code I've made here, which asks of a different problem I'd like to solve.

In case someone experiences this, holder.setIsRecyclable(false) will do it but then, it just makes the recycler view a list view, and also consumes more resources . Overriding the getItemViewType and getItemId should fix it.

As they said above
setIsRecyclable() is a bad choice and won't fix it.
Override the 2 methods getItemId & getItemViewType and return position for both this will fix it.

If I understand the question correctly, you don't want to scroll above when you call set adapter.
The best way I found is to call
YourRcv.swapAdapter(YourAdapter, Bolean removeAndRecycleExistingViews); // true if yes false if no
I found this method at line 1142 in package androidx.recyclerview.widget , class RecyclerView.java

Related

Show empty list items with loading animation until the whole list is loaded in android

I want to achieve something like this
And this
How can I show this kind of empty empty list as in udemy combined with some animation like in snapchat until whole list is loaded
Now that you explained a bit better I'm editing this answer.
In order to do something similar to SkyScanner, display fake placeholders because you can't retrieve any previous information about the data you must follow these steps:
For the animation have a look on Lottie library you can do a really nice animation, and make this animation run in loop until you have your call response, check the library here: https://github.com/airbnb/lottie-android
Lottie is a nice way to do vectors animation and it uses json file so it's really light, but you can also do the animations on the old style of course.
For the placeholder the logic is this: You are going to switch the content of your data set, the adapter is going to check, if the content inside the data set is the kind of real data you want to display it is going to fill with the real xml and use the real recyclerview holder, if the kind of that is not the real data you are going to inflate the row with the placeholder xml (the same size and disposition to achieve the result you are looking for) and a new recyclerview holder for placeholder so you can control the row behaviour, for example control your animation or intercale object behaviour based on position.
Consider an object YourCustomObject as the holder of your real data, but can be any kind as long you do a different kind of data for your fake placeholder, the logic inside the adapter is this:
#Override
public int getItemViewType(int position) {
return mDataSet.get(position) instanceof YourCustomObject ? VIEW_TYPE_DATA : VIEW_TYPE_PLACEHOLDER;
}
Then
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder holder;
if (viewType == VIEW_TYPE_DATA) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_real_data, parent, false);
holder = new RealDataHolder(v);
} else {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_placeholder, parent, false);
holder = new PlaceholderHolder(v);
}
return holder;
}
Don't forget to check the type of the Holder on onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof RealDataHolder) {
YourCustomObject data = (YourCustomObject) dataSet.get(position);
(...)
Don't forget that inside the adapter your dataSet is a list of Object (java class)
I hope it helps.

How to make RecyclerView stops recycling defined positions?

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 =(
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);
}
}
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);
}
...
}
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
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.
Best way to handle item not to recycle in recyclerview this answer will resolve your problem.
Not to recycle item
Try using this for that particular position:
holder.setIsRecyclable(false);
Hope this may help.
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

Accessing a list item's view and data at the same time

I am working on a messaging application and would like to set the chat bubble as coming from the left or the right depending on who the owner of the message is.
public class Message {
public String messageText;
public boolean mine;
// ...Constructor
}
Inside MessageAdapter I have:
#Override
public MessageHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(this.itemResource, parent, false);
return new MessageHolder(this.context, view);
}
#Override
public void onBindViewHolder(MessageHolder holder, int position) {
Message message = this.messages.get(position);
holder.bindMessage(message);
}
I have been following tutorials to migrate from ListView to RecyclerView and am now unclear as to how to access both an item's data and its view at the same time without getting NullPointerException. The code I want to use looks like this:
Drawable background = getResources().getDrawable(message.mine ? R.drawable.bubble_right : R.drawable.bubble_left);
background.setColorFilter(getResources().getColor(R.color.ColorPrimary), PorterDuff.Mode.SRC_IN);
view.findViewById(R.id.message_text).setBackgroundDrawable(background);
However, if I put it in onCreateViewHolder I can't figure out how to access the individual message to check ownership. If I put it in onBindViewHolder I have message but can't get its view. How do I solve this?
If you are moving from ListView to RecyclerView, then it's very easy to understand.
getView() (of ListView) == onCreateViewHolder() + onBindViewHolder();
If you remember we need to check view given by getView is null or not, and if it's null then we need to take care of instancing otherwise set the data to corresponding views.
RecyclerView are making our life easy, the onCreateView creates new instance of the view of each row(minimum required) and onBindViewHolder takes care of binding data to each view element.
I think now you are clear why and how to handle these two methods of RecyclerView.
As per your code, the line view.findViewById(R.id.message_text) should be moved to your ViewHolder class.
And the following lines will go in onBindViewHolder
Drawable background = getResources().getDrawable(message.mine ? R.drawable.bubble_right : R.drawable.bubble_left);
background.setColorFilter(getResources().getColor(R.color.ColorPrimary), PorterDuff.Mode.SRC_IN);
holder.your_view.setBackgroundDrawable(background);
Let me know if you want to know further clarifications.

RecyclerView and ViewHolder pattern - proper design approach

I've got RecyclerView list where items views are created partially from layout in .xml file (let's say header) and partially programmatically based on list items. I'm using few different itemsViewTypes.
Which approach is better?
1) to generate Views in adapter:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
createAndAddNewViews(barsHolder);
createAndAddNewViewsBasedOnSomeParams(param1, param2);
// both createAndAddNewViews() and
// createAndAddNewViewsBasedOnSomeParams() are Adapter methods
return holder;
}
2) to generate Views in ViewHolder:
public MyViewHolder(View itemView) {
super(itemView);
... // findViewsById and so on
createAndAddNewViews();
createAndAddNewViewsBasedOnSomeParams(param1, param2);
// both createAndAddNewViews() and
// createAndAddNewViewsBasedOnSomeParams() are ViewHolder methods
}
Of course my examples are simplified just to show the case.
From code reading perspective it's better to have views generation logic inside holders. Every different item has it's own creation code inside one class. I don't have to deal with every single item in Adapter. But I'm not sure if it's ok simply from ViewHolder pattern design and from memory and speed optimization perspective.
I wonder which approach you consider as better and why.
Edit: Does the same applies for modifying view (onBindViewHolder) and methods in Adapter/ViewHolder?
Thanks!
Performance wise it's the same in both the approaches. If you have different ViewHolders for different itemTypes you can do in in ViewHolder to segregate the code belonging to a certain ViewHolder at one place

Does notifydatasetchanged call onCreateViewHolder when using RecyclerView

I want to use a toggle to toggle between two different views but using the same RecyclerView. Basically, once you toggle, I want the RecyclerView adapter to recall onCreateViewHolder() but this time it will use a different layout item file.
Does notifydatasetchanged() cause the adapter to rebuild itself? Or is there another way?
I needed to have two types on Views on my RecyclerView Adapter as well, one for 'regular' mode and one for multi-select mode.
So, you can override getItemViewType to force the Adapter to call your onCreateViewHolder for all views.
Add this to the Adapter code:
public void setActionMode(ActionMode actionMode) {
this.actionMode = actionMode;
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
return (actionMode == null ? 0 : 1);
}
Add this to the ViewHolder:
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == 0) {
view = inflater.inflate(R.layout.layout_1, parent, false);
} else {
view = inflater.inflate(R.layout.layout_2, parent, false);
}
...
}
Since you return a different ViewType when in an ActionMode, the Adapter is forced to throw away all created views, and recreate everything again.
notifyDataSetChanged() calls onBindViewHolder() in case of RecyclerView
THE SIMPLEST SOLUTION
If you want to refresh RecyclerView items and onCreateView() be called too, say for Grid and List LayoutManagers.
void refreshRecyclerView(RecyclerView recyclerView){
Adapter adapterRef=recyclerView.getAdapter();
recyclerView.setAdapter(null);
recyclerView.setAdapter(adapterRef);
}
it will completely refresh the RecyclerView
//example usage
refreshRecyclerView(yourRecyclerView);
To remove and update layout in RecyclerView, you can call
mRecyclerView.removeView(view);
OR
mRecyclerView.removeViewAt(position);
after removing object in your dataset
I spent more than 6 hours on this issue without any success.
Finally!!!
I set a global variable in the adapter and had to set it up every time i toggled the view from list to grid (in my case). the funny thing this approauch was there but I forgot to do it as static!! So my solution could be related to yours , just try it and hope it works out.
public static int mCurrentViewType;
then override the getItemType()
#Override
public int getItemViewType(int position) {
return mCurrentViewType;
}
my toggleItemViewType method:
public void toggleItemViewType () {
if (mCurrentViewType == LIST_ITEM){
mCurrentViewType = GRID_ITEM;
} else {
mCurrentViewType = LIST_ITEM;
}
}
I am accessing the variable from different classes, which is not right, but for now and for the sake of the onCreateViewHolder issue, it worked!
if you have a better solution then good luck and share it with us.
don't forget to make the global variable as "static" :)
Yes it will assume that its current data set is invalid and would need to relayout and rebind all layouts.

Categories

Resources