ashutosh ashutosh - 23 days ago 5
Android Question

Generating multiple Gridviews dynamically

I am fetching some content from an API which I need to show in android app. The content comes in JSON format, which looks something like this:

{
"items": [
{
"catalog_items": [
{
"date": "23-01-2015",
"content": "Trimmer 1 description",
"name": "Trimmer 1"
},
{
"date": "25-01-2015",
"content": "Trimmer 2 description",
"name": "Trimmer 2"
}
.....
.....
],
"item_category": "Trimmer"
},
{
"catalog_items": [
{
"date": "13-08-2014",
"content": "Shirt description here",
"name": "John Player Shirt"
}
],
"item_category": "Shirts"
},
{
"item_category": "Woolen"
}
],
"pages": [
{
"date": "24-01-2015",
"content": "This is some content about page 1",
"name": "Sample Page title 1"
},
{
"date": "26-01-2015",
"content": "This is some content about page 2",
"name": "Sample Page title 2"
}
]
}


I have to create a dashboard in app which is built up in following manner, based upon above JSON data:

Top Menu
=====================
Trimmers (Gridview)

Trimmer1 Trimmer2
Trimmer3 Trimmer4

======================
Shirts (Gridview)

John Players

======================
Pages (Listview)

Page1
Page2


My Dashboard Fragment fetches this JSON. My Dashboard layout is:

fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</RelativeLayout>


and My Dashboard Activity looks like this:

public class HomeFragment extends Fragment {
.....
.....
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
try {

JSONObject obj = new JSONObject(loadJSONFromAsset("input.json"));
if(obj.has("catagories")) {
JSONArray catag = obj.getJSONArray("items");
for(int i=0; i< catag.length();i++){
JSONObject catitem = catag.getJSONObject(i);
JSONArray posts = catitem.getJSONArray("catalog_items");
for(int j=0;j<posts.length();j++){
JSONObject postitem = posts.getJSONObject(j);
catList.add(postitem.getString("name"));
}

addGridtoLayout(catitem.getString("item_category"),catList);
}
}
} catch (JSONException e){
e.printStackTrace();
}

return rootView;

}
public void addGridtoLayout(String title, ArrayList<String> itemList)
{
RelativeLayout ll = new RelativeLayout(getActivity());
RelativeLayout.LayoutParams parambs = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
ll.setLayoutParams(parambs);


TextView tv = new TextView(getActivity());
tv.setText(title);
tv.setId(R.id.layout1);

RelativeLayout.LayoutParams lay = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
ll.addView(tv, lay);

GridView gridView= new GridView(getActivity());
gridView.setLayoutParams(new GridView.LayoutParams(GridLayout.LayoutParams.MATCH_PARENT, GridLayout.LayoutParams.MATCH_PARENT));
gridView.setNumColumns(GRID_COL_NUM);
gridView.setAdapter(new HomeGridAdapter(getActivity(), itemList));
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, tv.getId());
ll.addView(gridView, params);

mainLayout.addView(ll);
}


This is the code only for items (Gridview).

What I am going is basically wrapping each gridview and the title in a relativelayout. And the relative layouts would appear below the former one, starting from first item.
But gridviews are getting stacked over each other and all items are being shown in a single row.

Is there any method so I can define the relativelayouts of each gridview to appear below the previous one? Also, is there any better approach to achieve this? I mean the basic requirement is to generate dynamic number of gridviews. So is generating layout on the fly is only way to do this?

Thanks for reading this long post.

Answer

Well, as I mentioned in comment, I made slightly modified code and that works really awesome. So I am going to elaborate what I did. Maybe it could help someone out.

Since I have to generate multiple sectioned Gridviews, I initially thought of generating tables using TableLayout and adding TableRows to it. Each Tablerow would be holding a RelativeView consiting of:

  1. Item's image (from JSON)
  2. Item's name (from JSON)

So, I initially created this code:

HomeFragment.java

public class HomeFragment extends Fragment {
   .....
   .....

    ArrayList<String> itemList;
    private JsonHelper jsonHelper;
    private static final int GRID_COL_NUM = 3;
    TableRow tbh;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_home, container,false);
    TableLayout table = (TableLayout) rootView.findViewById(R.id.tableLayout);
    table.setStretchAllColumns(true);
    table.setShrinkAllColumns(true);

    try {
        JSONObject obj = new JSONObject(loadJSONFromAsset("input.json"));
        if(obj.has("catagories")) {
           JSONArray catag = obj.getJSONArray("items");
           for(int i=0; i< catag.length();i++){
              JSONObject catitem = catag.getJSONObject(i);
              JSONArray items = catitem.getJSONArray("catalog_items");
              if(items.length() > 0) {
                 //Add a Grid Title for this category (span=6, full width)
                 TableRow tbr = addRowTitle(catitem.getString("cat_name"));
                 table.addView(tbr); //Add this title row to the table

                 itemList = new ArrayList<>(); //Hold all items for each category
                 for(int j=0;j<items.length();j++){
                     JSONObject singleItem = items.getJSONObject(j);
                     //I am trying to show only 3 items in a row, so add new row after 3
                     if(j % 3 == 0){
                        tbh = addRowGrid(); //add new row and repeat so after 3
                         table.addView(tbh); //Add this to the table, we will fill items below
                     }

                     LinearLayout lnr = new LinearLayout(getActivity());
                     TableRow.LayoutParams params = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,TableRow.LayoutParams.WRAP_CONTENT);
                     params.gravity = Gravity.CENTER; //Center the items
                     lnr.setLayoutParams(params);
                     lnr.setOrientation(LinearLayout.VERTICAL); //As per your need. worked for me

                     //Item's Image
                     ImageView itemImg = new ImageView(getActivity());                          itemImg.setImageResource(R.drawable.ic_communities); //Default icon for item


                     TextView itemLabel = new TextView(getActivity());
                     itemLabel.setText(singleItem.getString("name"));
                     itemLabel.setTypeface(Typeface.SERIF, Typeface.BOLD);

                     lnr.addView(postImg,params);
                     lnr.addView(postLabel,params);
                     tbh.addView(lnr); //Now we have image and content, wrap in linear layout and add to the row.
                     itemList.add(singleItem.getString("name")); //This step is not needed for now. I just wrote this to use this array for other purposes.
                  }
        //I am all confused at this point if I have really closed all of curly braces. Please confirm
               }
            }
         }
     } catch (JSONException e){
            e.printStackTrace();
     }

     return rootView;

  }

  public TableRow addRowGrid()
  {
        return new TableRow(getActivity());
  }

  public TableRow addRowTitle(String titleb)
  {
        TableRow rowTitle = new TableRow(getActivity());
        rowTitle.setGravity(Gravity.CENTER_HORIZONTAL);
        // title column/row
        TextView title = new TextView(getActivity());
        title.setText(titleb);

        title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
        title.setGravity(Gravity.CENTER);
        title.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TableRow.LayoutParams params = new TableRow.LayoutParams();
        params.span = 6;

        rowTitle.addView(title, params);
        return rowTitle;
  }
...
...
}

This whole code works for a single Table layout defined in layout: fragment_home:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_scrollview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true">
<RelativeLayout
    android:id="@+id/relativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TableLayout
        android:id="@+id/tableLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"></TableLayout>

</RelativeLayout>
</ScrollView>

So the whole table is generated on the fly. Worked pretty well. I can attach a click handler to each row to listen, and I might have to set tag for each views to get the clicked item. Its then I thought to more feasible and flexible approach. What if I add the gridview to each row itself, instead of appending LinearLayouts to each TableRows? So, I ended up on modifying my code on this approach: 1. A single TableRow will hold each category title and a second Tablerow will be appended to it, which will hold the gridview. 2. I can now use a Custom Grid Adapter by which I could change layout of each item better, i.e via XML instead of tingling here "on-the-fly" approach. 3. Above point also gained me a clicklistener. So no setting of tag required.

NOTE: The above approach was working as well as below approach is working too (as same). So you can use both

Hence, I create a function that would add title to each category of item and a function to add items to grid, and grid to a row:

HomeFragment.java (gridview version)

public class HomeFragment extends Fragment {

    private static final int GRID_COL_NUM = 3;
    TableRow tbh;

    public HomeFragment(){}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_home, container, false);
        TableLayout table = (TableLayout) rootView.findViewById(R.id.tableLayout);
        table.setStretchAllColumns(true);
        table.setShrinkAllColumns(true);

        try {

            JSONObject obj = new JSONObject(loadJSONFromAsset("input.json"));
            if(obj.has("catagories")) {
                JSONArray catag = obj.getJSONArray("catagories");
                for(int i=0; i< catag.length();i++) {
                    JSONObject catitem = catag.getJSONObject(i);
                    if (catitem.has("items")) {
                        JSONArray items = catitem.getJSONArray("catalog_items");
                        if (items.length() > 0) {
                            //Add row with title catname
                            TableRow tbr = addRowTitle(catitem.getString("cat_name")); //Add category Name as title
                            table.addView(tbr); //Same I did previously

                            ArrayList<String> itemList = new ArrayList<>();
                            for (int j = 0; j < items.length(); j++) {
                                JSONObject singleItem = posts.getJSONObject(j);
                                itemList.add(singleItem.getString("name"));
                            }

                            tbh = addGridRow(itemList); //Create Grid of collections and add it to a tablerow
                            table.addView(tbh); //Add this tablerow to table
                        }
                    }
                }
            }
            if(obj.has("pages")) {
                JSONArray pages = obj.getJSONArray("pages");
                if(pages.length() > 0) {
                    TableRow tbr = addRowTitle("Pages");
                    table.addView(tbr);

                    ArrayList<String> pageList = new ArrayList<>();
                    for (int i = 0; i < pages.length(); i++) {
                        JSONObject page = pages.getJSONObject(i);
                        pageList.add(page.getString("name"));
                    }
                    tbh = addGridRow(pageList);
                    table.addView(tbh);
                }
            }
        } catch (JSONException e){
            e.printStackTrace();
        }

        return rootView;
    }

/**
* Now this is a life saver. Gridviews aren't friendly with scrollviews (I might be wrong, but happens with me everytime), especially if generated dynamically. Hence this function calculates height of each item of gridview dynamically and hence sets the total view height adapted from http://stackoverflow.com/a/22555947/1136491
**/
    public void setGridViewHeightBasedOnChildren(GridView gridView, int columns) {
        ListAdapter listAdapter = gridView.getAdapter();
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = 0;
        int items = listAdapter.getCount();
        int rows = 0;

        View listItem = listAdapter.getView(0, null, gridView);
        listItem.measure(0, 0);
        totalHeight = listItem.getMeasuredHeight();

        float x = 1;
        if( items > columns ){
            x = items/columns;
            rows = (int) (x + 1);
            totalHeight *= rows;
        }

        ViewGroup.LayoutParams params = gridView.getLayoutParams();
        params.height = totalHeight;
        gridView.setLayoutParams(params);

    }

    public TableRow addGridRow(ArrayList gridItemList)
    {
        TableRow gridrow = new TableRow(getActivity());
        GridView gv = new GridView(getActivity());
        TableRow.LayoutParams params = new TableRow.LayoutParams(
                TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT);
        gv.setLayoutParams(params);
        gv.setNumColumns(GRID_COL_NUM); //3 columns grid 
        gv.setAdapter(new HomeGridAdapter(getActivity(), gridItemList)); //Custom grid adapter I was talking about. See Below
        gridrow.addView(gv);
//Thanks for not messing up my gridview inside scrollview
        setGridViewHeightBasedOnChildren(gv,GRID_COL_NUM); //3 Columns
        return gridrow;
    }

//Simply add title above the gridview
    public TableRow addRowTitle(String titleb)
    {
        TableRow rowTitle = new TableRow(getActivity());
        rowTitle.setGravity(Gravity.CENTER_HORIZONTAL);
        // title column/row
        TextView title = new TextView(getActivity());
        title.setText(titleb);

        title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
        title.setGravity(Gravity.CENTER);
        title.setTypeface(Typeface.SERIF, Typeface.BOLD);

        TableRow.LayoutParams params = new TableRow.LayoutParams();
        params.span = 6;

        rowTitle.addView(title, params);
        return rowTitle;
    }
....
}

Now my custom Adapter HomeGridAdapter.java

public class HomeGridAdapter extends BaseAdapter {
    ArrayList<String> result;
    Context context;

    private static LayoutInflater inflater=null;
    public HomeGridAdapter(Context context, ArrayList<String> itemList) {
        // TODO Auto-generated constructor stub
        this.result=itemList;
        this.context=context;
        inflater = ( LayoutInflater )context.
                getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return result.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    public class Holder
    {
        TextView tv;
        ImageView img;
    }
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        Holder holder=new Holder();
        View rowView;
        //Layout mentioned below
        rowView = inflater.inflate(R.layout.home_grid_layout, null);
        holder.tv=(TextView) rowView.findViewById(R.id.textView1);
        holder.img=(ImageView) rowView.findViewById(R.id.imageView1);
        //Item's Text
        holder.tv.setText(result.get(position));
        //Item's Image
        holder.img.setImageResource(R.drawable.ic_launcher);
        //A click listener to each gid item
        rowView.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Toast.makeText(context, "You Clicked "+result.get(position), Toast.LENGTH_SHORT).show();
            }
        });

        return rowView;
    }

}

home_grid_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="vertical" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_gravity="center"
        android:layout_width="88dp"
        android:layout_height="88dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/textView1"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:text="TextView" />

</LinearLayout>

So I am currently using Gridview version of the TableLayout. I might have achieved what I wanted, but I would really welcome any suggestions here. Hope this answer serves someone a great help.

Thanks for bearing this long.