0

The scenario

I'm trying the create something akin to the featured page on Google Play Store. But instead of showing three items for a category I'm allowing it show any number of items in a two column staggered grid view fashion.

So each list item has a header that has a title and a description followed by a custom view (lets call this SVG, as in Staggered View Group) that shows some number of children views in a staggered grid view fashion.

I have a class called FeaturedItems that hold the data for a row in the featured list. Here is an extract:

public class FeaturedItems {

private String mName;
private String mDescription;
private ArrayList<Object> mList;

public FeaturedItems(String name, String description, Object... items) {
    mName = name;
    mDescription = description;
    mList = new ArrayList<Object>();
    for (int i = 0; i < items.length; i++) {
        mList.add(items[i]);

    }
}

public int getItemCount() {
    return mList.size();
}

public Object getItem(int position) {
    return mList.get(position);
}

public String getFeatureName() {
    return mName;
}

public String getFeatureDescription() {
    return mDescription;
}

}

The FeaturedListAdapter binds the data with the views in the getView() method of the adapter. The getView() method is as follows:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    FeaturedItems items = getItem(position);

    if (convertView == null) {
        LayoutInflater infalInflater = (LayoutInflater) this.mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = infalInflater.inflate(mResource, null);

        holder = new ViewHolder();
        holder.title = (TextView) convertView.findViewById(R.id.list_item_shop_featured_title);
        holder.description = (TextView) convertView.findViewById(R.id.list_item_shop_featured_description);
        holder.svg = (StaggeredViewGroup) convertView.findViewById(R.id.list_item_shop_featured_staggered_view);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.title.setText(items.getFeatureName());
    holder.description.setText(items.getFeatureDescription());

    // HELP NEEDED HERE
    // THE FOLLOWING PART IS VERY INEFFICIENT
    holder.svg.removeAllViews();

    for (int i = 0; i < items.getItemCount(); i++) {
            FeaturedItem item = new FeaturedItem(mContext, items.getItem(i));
            item.setOnShopActionsListener((ShopActions) mContext);
            holder.svg.addView(item);
    }

    return convertView;
}

The problem

In the getView() method, each time a view is returned, it removes all the child views in the SVG and instantiates new views called FeaturedItem that are then added to the SVG. Even if the SVG in a particular row, say first row, was populated, when the user scrolls back to it from the bottom, the getView() method will remove all the children views in the SVG and instantiates new views to be populated with.

The inefficiency here is very obvious, and the list view animation skips frames when scrolled quite often.

I can't just reuse the convertView here because it shows the wrong featured items in the StaggeredViewGroup. Therefore I have to remove all children from the StaggeredViewGroup and instantiate and add the views that are relevant to the current position.

The question

Is there a way around this problem? Or are there some alternative approaches to creating a page similar to the Google Play Store featured page, but with each row having different number of featured items thus having its unique height?

fahmy
  • 3,543
  • 31
  • 47
  • Why do you have a "StaggeredViewGroup" inside the inflated view? Is it like a horizontal listview ? – android developer Jun 13 '14 at 21:13
  • Its a custom ViewGroup that arranges children like this: http://goo.gl/vV2yzg – fahmy Jun 14 '14 at 04:11
  • 1
    I might not be familiar enough with this kind of adapterView, but I think you are doing it wrong. Can you please show which library you are using, so that I could check out how you should handle it? Also, will this post be of any help: http://stackoverflow.com/questions/18557720/gridview-with-different-cells-sizes-pinterest-style ? – android developer Jun 14 '14 at 08:16
  • I'm not using a library here. To clear up the confusion, the adapter view is just a custom adapter extended from the `BaseAdapter`. Its takes an xml layout and inflates it in the `getView()` method if necessary. The `StaggeredViewGroup` is a custom view that extends `ViewGroup`. It simply arranges its children like a this goo.gl/vV2yzg. The `StaggeredViewGroup` is in the xml layout that the adapter uses as a row in the list view. After inflating (or reusing) the `convertView`, the featured items for the row are instantiated and added to the `StaggeredViewGroup`. – fahmy Jun 14 '14 at 17:18
  • Btw, I found something that could potentially work as an alternative solution. So thanks! – fahmy Jun 14 '14 at 17:21
  • What is the type of the adapterView ? it sounds weird for me to use a vertically oriented view inside an adapterView, while most of the adapterViews are vertical anyway... – android developer Jun 14 '14 at 17:57
  • It's an `android.widget.ListView`. Yup you are right, it is weird! I can't get it to look like a staggered view with category titles and stuff if I don't do that. But I'm trying to check and see if `AndroidStaggeredGrid` (library from the link you shared) would do the trick. – fahmy Jun 14 '14 at 19:54
  • 1
    Oh, you want to create staggeredGrid, and have titles in it? Interesting... Maybe it's best to create your own AdapterView, though this could take a long time. I don't think any of the libraries has this feature. You could modify them of course. Anyway, here's another alternative to the staggeredViewGroup : http://stackoverflow.com/questions/4474237/how-can-i-do-something-like-a-flowlayout-in-android . If you don't have a lot of items per chunk, maybe it's ok to do it. – android developer Jun 14 '14 at 20:34

1 Answers1

2

There should be an easy way to improve this solution. Just reuse the svg children that are already present, add new ones if they are not enough, and then remove any surplus ones.

For example (in semi-pseudocode, method names may not be exact):

for (int i = 0; i < items.getItemCount(); i++)
{
    if (i < svg.getChildCount())
    {
        FeaturedItem item = i.getChildAt(i);

        // This item might've been set to invisible the previous time
        // (see below). Ensure it's visible.
        item.setVisibility(View.VISIBLE);

        // reuse the featuredItem view here, e.g.
        item.setItem(items.getItem(i));
    }
    else
    {
        // Add one more item
        FeaturedItem item = new FeaturedItem(mContext, items.getItem(i));
        ...
        holder.svg.addView(item);
    }
}

// hide surplus item views.
for (int i = items.getItemCount(); i < svg.getChildCount(); i++)
    svg.getChildAt(i).setVisibility(View.GONE);

/** 
  as an alternative to this last part, you could delete these surplus views
  instead of hiding them -- but it's probably wasteful, since they may need 
  to be recreated later

while (svg.getChildCount() > items.getItemCount())
    svg.removeChildView(svg.getChildCount() - 1);
**/
matiash
  • 54,791
  • 16
  • 125
  • 154