1

I am trying to build a horizontal recycler view and each item is the same parent type but can include up to ten different subviews attached to it. I have looked at this answer which explains how to override

getItemViewType(int position)

and

createViewHolder(ViewGroup parent, int viewType)

But this only works if you have a limited number of views for each item. The list of items I have to display can have a size of 40 and each one can include up to 8 images each in a different position on the parent view.

My original plan was to create each item view dynamically in the Recycler View Adapter in

onBindViewHolder(ViewHolder viewHolder, int i)

This caused issues because I had to create all the individual views and attach them to the parent view each time I saw the item. 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.

My problem now is

  1. It is still not as smooth as I would like. If you have any solutions or ideas on how I could do this faster or more proper. let me know.

Here is an example image of three possible different views for the recycler view. So these views only include the photos and shapes. But other views include text and colors.

Current 3 different views in recyclerview

Community
  • 1
  • 1
ganlaw
  • 1,195
  • 4
  • 17
  • 32
  • 1
    What do you mean "But this only works if you have a limited number of views for each item." ? There's no upper limit on the number of different types of views you can make. What was the problem when you tried it that way? – JoeyJubb Sep 30 '15 at 14:06
  • In the link I posted they check for each itemViewType and if it matches a specific type. Then they just use that xml file. So if I have a possibility of over 2000 different views that it could be this way would not work. I am building these views from dimensions I get programmatically. – ganlaw Sep 30 '15 at 14:10
  • 2000? Cor blimey! I think a good idea then could be by using a custom view group that reacts appropriately the the views given to it – JoeyJubb Sep 30 '15 at 14:12
  • I tried that but there are way to many different variations and this view data comes from the server and can change at any time so I need to be able to build it from the server details programmatically. Some have different colour backgrounds, different font with text in different spots and so many different size and shapes of images. – ganlaw Sep 30 '15 at 14:17
  • If the server controls how it looks, you might as well replace the list with a webview and let the server show it. I'm not recommending this as a solution though -- it would be better if you were able to write down your requirements, look at what's important, and narrow down your different view types to something more manageable! – JoeyJubb Sep 30 '15 at 14:20
  • Yeah, web view is defiantly not viable. The solution I have now that builds the views in the activity then passes them into the adapter, works alright (Slow at times) I was seeing if anyone had any better ideas on how to solve my problem. Thanks for help. Mind throwing on up vote to get more visibility? – ganlaw Sep 30 '15 at 14:22
  • I can't buddy sorry - I think your problem is in the design rather than implementation. 2000, 200 or even 40 different view types is just crazy for any kind of list! – JoeyJubb Sep 30 '15 at 14:26
  • What make your types so différent? –  Sep 30 '15 at 15:04
  • 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. – ganlaw Sep 30 '15 at 15:51
  • I added a picture of 3 different views in part of the list i currently have. – ganlaw Oct 05 '15 at 02:57
  • "It is still not as smooth as I would like" -- use Traceview to determine where your problem lies. – CommonsWare Oct 05 '15 at 12:44
  • what kind of data you get from your server? Maybe you can implement a way to "render" view and it would solve your problem. For example imagine a JSON contains an image object and text object with its size and coordinates. This way you can render any item for your recycler.( This would slow down your scrolling speed.) – Ercan Oct 06 '15 at 06:07
  • It seems like you have a specific set of requirements for these views and not actually 2000 different view types. If the differences will be a different amount of images, borders, buttons, etc. then you should have one view type that includes all of the possibilities and then hide/remove views as necessary. Although, having so many view variations in a single list generally leads to quite bad usability. – Marcus Hooper Oct 06 '15 at 11:57
  • Putting all of the views and just hiding and displaying them is not possible because I am creating the views based on information from the server. So if that changes then so does the view. And the information on the server changes all the time so the views will change all the time – ganlaw Oct 06 '15 at 15:13

2 Answers2

3

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

  1. Images (max 2) id: 0
  2. Images (max 4) id: 1
  3. Images (max 6) id: 2
  4. Images (max 8) id: 3

And

  1. Texts (max 2) id: 0
  2. 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.

Inverce
  • 1,387
  • 12
  • 26
  • 1
    I like this answer. Thank you for your time to type it all out. I am using UIL to load images/bitmaps currently. I was thinking about using a scrollview and linear layout but you are right about the memory, my phone can't handle when i have a lot of views which contain a lot of images. I do like the idea to just hide and show stuff when needed but I will need a lot of default views to start with, due to the amount and difference of each view. I will try it this weekend and let you know how it worked. A big rewrite is needed. Cheers – ganlaw Oct 06 '15 at 19:33
0

1 Create a list of lists each item should contain 1 or more image

    List<List<image>> list 

2 pass it to your adapter

3 in the getItemView method use if to set the type according to the

    list.get(position).size();

4 use if again or isinstanceof or I think the viewHolder has a method to get its class and cast your viewHolder to the right class .

Mohamed Allam
  • 125
  • 2
  • 10
  • But this would only work if I only had images in these views, there are also text and shapes of different sizes and colours located in different locations in the item view. – ganlaw Oct 05 '15 at 03:30