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
Related
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.
I have taken an example code for implementing a RecyclerView, but trying to transpose it to work in a Child Fragment in my app.
The code is under 'Create Lists - examples'
Creating Lists and Cards
I run into trouble with my adapter..
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
> #Override
> public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
> int viewType) {
> View v = LayoutInflater.from(parent.getContext())
> .inflate(R.layout.my_text_view, parent, false);
> ViewHolder vh = new ViewHolder(v);
> return vh;
> }
First, it doesn't build, complaining that I am calling constructor ViewHolder with a View, when the constructor is expecting a TextView.
Looking at the code, I agree!
But this is an official example so it must be right?
So what is different to my version compared with the example?
Two things that I can think of...
1)
the layout my_text_view is not given in the example, so I made my own. Did I make it right?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/t_title"
android:title="title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
2)
I am calling the adapter from my child fragment, and the example was not written with fragments in mind.
I've probably bitten off more than I can understand there.
Still, working this through, as far as i understand.
The 'parent' coming into my OnCreateViewHolder, is my RecyclerView ?
'v' should be the LinearLayout of my 'my_text_view' ?
'ViewHolder' should end up being a class with property mTextView equaling the TextView from my xml.
I just don't see how I go from v=LinearLayout , to TextView??
Anyone like to help explain to this noob ??
That example is not very good. It looks like two different projects spliced together. It should be something like this:
public class MyAdapter extends RecyclerView.Adapter {
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(View v) {
super(v);
mTextView = (TextView) v.findViewById(R.id.t_title);
}
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
}
To explain this segment:
onCreateViewHolder is the method that will be called any time the RecyclerView needs to create a new type of ViewHolder. This may get called only a dozen or so times to get enough Views to cover the screen or it may be called quite a few times if you have many view types.
The parameter parent in this case will be the RecyclerView that accompanies it. Why not just make it RecycerView? Because Google designers decided it should be a ViewGroup. Also it's an Adapter pattern so the only guarantee you're supposed to have is that it's a ViewGroup (i.e., may not be a RecyclerView so you shouldn't build the Adapter with that assumption). Realistically, it will pretty much always be a RecyclerView though.
The parameter int viewType is to tell you what kind of View you're building. This is determined if you override getItemViewType() method of the Adapter. You don't need to worry about this if you only have one type of View though.
For the ViewHolder, this is to basically cache the different types of Views in your layout. These can be ImageView, TextView, etc. These will be "recycled" constantly as the user scrolls so you're not always creating them. That way, you only have to update the Views with the current information. In this case, there's just a title. The ViewHolder passed in bindViewHolder will be where the actual updating happens. This is called all the time, so there's no need to initialize the Views in onCreateViewHolder. Just need to create them.
To show a list of items I usually use a RecyclerView but I have a requirement that makes it easier for me to use a vertical LinearLayout. I don't want to loose the good resource management of the RecyclerView so I'm looking for a different solution and hope someone can help me.
I think of a layout with some views on top, then a list of items followed by some more views on the bottom. Or a layout that contains more than one RecyclerView. So you have some Views on the top, a list of items followed by some more views and another list of items etc.
My layout could look like this:
View1
View2
RecyclerView
View3
RecyclerView
View4
Usually a RecyclerView works like a scrollable frame in which you can scroll some content. It means that if you start scrolling, your whole screen gets stuck in a certain position and you are just scrolling in the RecyclerView until its end. Then you can continue scrolling the whole screen.
What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView.
Does anybody know of a solution of this?
add below code to your custom adapter of recycler view
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null);
return new MyNormalViewHolder(normalView); // view holder for normal items
} else if (viewType == ITEM_TYPE_HEADER) {
View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null);
return new MyHeaderViewHolder(headerRow); // view holder for header items
}
}
What i suggest is to use a single recyclerview with custom adapter and inflate each row of recyclerview with diff layout ie.... based on your condition
In this situation i use only one RecycleView implementing my own getItemViewType
The best answer will depend on what behaviour you want for the screen as a whole.
"What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView."
This requirement makes me think that the best option indeed is a single RecyclerView using different ViewHolder types like #Rissmon Suresh and #betorcs mentioned.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class HeaderViewHolder extends RecyclerView.ViewHolder {
...
}
class NormalViewHolder1 extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
// use position to decide what kind of View you want
// can be fixed or you can access the data to check dinamically
if(position == 0)
return 0;
else
return 1;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new HeaderViewHolder(...);
case 1: return new NormalViewHolder1(...);
//...
}
}
}
Hope this helps =)
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
I'm using RecyclerView to create a list, and i was suffering a lot, because i saw different ways to implement an OnClickListener, and comments says like very coupled, very tricky, etc.
And about several days thinking how to implement it, i did this.
On my Fragment, implement the View.OnClickListener and the onClickEvent and when i'm implementing the RecyclerView.Adapter pass the View.OnClickListener reference in the constructor, like this:
public RecyclerViewRepoListAdapter(View.OnClickListener fragmentListener, List<Repos> repoList){
if (repoList != null){
this.reposList = repoList;
this.fragmentListener = fragmentListener;
}
}
After that, when i'm creating the Holder to the different list components, i register the Listener to every (in this case) LinearLayout like this:
#Override
public ListItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Inflate the corresponding layout
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_repositories, parent, false);
//OnClickListener
itemView.setOnClickListener(fragmentListener);
//Here modify the view like margins, paddings, view size and layout parameters
//return the ViewHolder
return new ListItemViewHolder(itemView);
}
My cuestion is, what is your opinion about this implementation?
And if its ok, i hope this helps another one.