Each item view has the option for up to 8 images, borders or no borders around >these images, up to 4 text boxes with striped, solid line, no borders, clip art >and the option for different colours on every one view items. Also different >sizes and different positions on each item. The possibilities are pretty much endless of what the item can look like
We can actually simplify this to
I have view with maximum 8 images and 4 text, that can have different
style and position.
Then there is only 45 possibilities for picking how many images and text there will be (counting possibility of no image or text).
So if i make simple RelativeLayout with enought elements, and then will move them as we need...
Hovewer saying 45 possible types are well...
Way too much. You see making any adapter carry too many item types are not very smart, simply put when adapter is set for recycler view (or ListView) one of first thing it does is creating "small" ViewPool (RecyclerViewPool), its basically simple sparse array , that will keep any removed ViewHolder, until its time to be reused will come, and if the heap is too big it will be removed. That is theory, in practice there are limit of how many scap childs can be at specified time (for recycler its around 5 i think), so if we will be creating view holders with many types we want be hitting that cache most of the time, and since there is no scrap view holder for our type createViewHolder will be called, and in fact this will feel more like we dont have view holder at all.
One more thing.
So then I created a list of views in the activity which holds the recycler view >and instead of passing in the list of objects to create the views, I just >passed in the views themselves. This worked much better due to the fact I never >had to read each item every time and create a custom view for each item every >time it loaded.
Recycler view is used to to unload items (views) that are no longer needed, or should i say that are not visible to user, and also to reause old views us much as possible to reduce number of layout inflatings, and searches for id.
And by loading them all at all times, this functionality is ommited.
If im not mistaken this might be just a bit slower than creating ScrollView with LinearLayout (Vertical) and putting all views inside.
Hovewer biggest drawback here is that all your views are loaded at all times, and if they are simple i dont really see why not use ScrollView + LinearLayout, but if not well this gets complicated, on new phones, memory is not such big problem, there are phones with 500+ mb, but even so not every phone will give you 300 mb for your application.
My Answer
I can guess it want be what you wanted to hear :)
The thing that slows down layout inflating, isnt actually setting texts, background or position but loading resources to do that, and searching for views.
So lets start with:
Use cache for your loaded resources to reuse them us much as you can, (i added small section about image loading at the end).
FindViewById is really slow, i mean it, just by using viewholder pattern and removing searching for view you can get a nice speed up.
You want be getting away from setting layout for each element.
Idea is pretty simple create groups of layouts depending on amount of your main hierarchy Views (this time) Images and text. While creating ViewHolder create RelativeLayout, push enough images/text to suit your need for element at position. While binding data move elements and style them as needed.
To reduce number of types, lets use number of images/text rounded to 2.
Updating LayoutParams does not cost us much as most think, it cost some, dont get me wrong, this basically forces parent to make additional child measurement and layout update, thats all, and while view is going to be diplayed it has to make this measurement and layout update anyway :)
Lets say we have this two groups
- Images (max 2) id: 0
- Images (max 4) id: 1
- Images (max 6) id: 2
- Images (max 8) id: 3
And
- Texts (max 2) id: 0
- Texts (max 4) id: 1
And this is how i will be "naming" types
int type = (img_id)<<1 | (txt_id)
And total type count is actually
int type_count = (4 * 2) = 8
End our "base" for adapter is something like this
public class MyAdapter<MyType extends MyAdapter.MyTypeBase> extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
Context context;
ArrayList<MyType> elements;
public MyAdapter(Context context, ArrayList<MyType> elements) {
this.context = context;
this.elements = elements;
}
public int getItemCount() { return elements.size(); }
public MyType get(int position) { return elements.get(position); }
public int getItemViewType(int position) {
int imgs = get(position).getImageCount();
int txts = get(position).getTextCount();
imgs = (imgs/2) * 2 + (imgs % 2 == 0 ? 0 : 1);
txts = (txts/2) * 2 + (txts % 2 == 0 ? 0 : 1);
return imgs << 1 | txts;
}
@Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
return new ViewHolder(new RelativeLayout(context), position); // or use inflater to inflate some base layout
}
class ViewHolder extends RecyclerView.ViewHolder {
int textCount, imageCount;
RelativeLayout root;
ImageView [] imageViews;
TextView [] textViews;
public ViewHolder(RelativeLayout view, int position) {
super(view);
root = view;
textCount = (position & 1) * 2;
imageCount = (position >> 1) * 2;
textViews = new TextView[textCount];
imageViews = new ImageView[imageCount];
for (int i = 0; i < imageCount; i++) {
root.addView(imageViews[i] = new ImageView(context)); // or use inflater
}
for (int i = 0; i < textCount; i++) {
root.addView(textViews[i] = new TextView(context)); // or use inflater
}
}
}
interface MyTypeBase {
int getImageCount();
int getTextCount();
}
And yes somethings is missing :)
public void onBindViewHolder(ViewHolder viewHolder, int position) {
MyTypeBase element = get(position);
vh.textViews[vh.textCount-1].setVisibility(element.getTextCount()%2 == 0? View.VISIBLE : View.GONE);
vh.imageViews[vh.imageCount-1].setVisibility(element.getImageCount()%2 == 0? View.VISIBLE : View.GONE);
// now this is your place to shine
}
Since i dont know what exactly you can get as "data" for your element cant help you much here. So in on bindViewHolder, you viewHolder will have enough images and text as your element needs, reload any image and move them.
Last notes
I should also add fev notes here and there :)
Be lazy. Yes i mean it when you want to be lazy you try to make your solution as simple as possible making code more easy to read, and less likely to have major bugs :)
Also please use library for displaying and caching images instead of loading them each time. There are quite few good ones like UniwersalImageLoader, Picasso or Volley. I personally use UIL so heres example.
// This might be done in helper class ^^
DisplayImageOptions OPTIONS_DISC_AND_CACHE = new DisplayImageOptions.Builder()
.cacheOnDisk(true)
.cacheInMemory(true)
.build();
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
// .memoryCacheSize((int) (Runtime.getRuntime().maxMemory() / 1024) / 16) // if you want to reduce it some more ^^
.threadPoolSize(4)
.tasksProcessingOrder(QueueProcessingType.LIFO)
.build();
imageLoader.init(configuration);
// And thats how to use it
ImageLoader.getInstance().displayImage("MY URL", (ImageView) myImageView, Tools.OPTIONS_DISC_AND_CACHE);
And why you should use such library, dunno... Images are loaded asynchronusly (not on ui, making it more resposible), you dont care for how they are actually loaded, and when, adding placeholders is easy, cache in memory and cache in for disk allows for faster loading times.
And lastly, english is not my primary language, so if i misspelled something, forgot comma, or wrote sentence in wrong manner, please do make corrections.
Cheers.