Saturday, 27 May 2017

Android: GridView as ExpandableListView Item

In this post I will show you how to use a GridView as the item of an ExpandableListView.
An ExpandableListView is a view that shows items in a vertically scrolling two-level list.
This differs from the ListView by allowing two levels: one are the groups and other are the child items. The Groups can individually be expanded to show its children.

Now the implementation of using a GridView as an item of ExpandableListView is as follows:

1. First of all make a custom gridView class named MyCustomGridView.java and add the following code snippet to it:

public class MyCustomGridView extends GridView {

    private final Context mContext;
    int mHeight;

    public MyCustomGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int desiredWidth = 100;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int width;
        // Measure Width
        if (widthMode == MeasureSpec.EXACTLY) {
            // Must be this size
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            // Can't be bigger than...
            width = Math.min(desiredWidth, widthSize);
        } else { // Be whatever you want
            width = desiredWidth;
        }
        // MUST CALL THIS
        setMeasuredDimension(width, mHeight);
        getGridViewSpanCount(width, mHeight);
    }

    public void setGridViewItemHeight(int height) {
        mHeight = height;
    }
}

2. Now make the layout file named gridview_item.xml which will be used as item of GridView:

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

    <ImageView
        android:id="@+id/item_gridView_imgView"
        android:layout_width="@dimen/dimen_img_width"
        android:layout_height="@dimen/dimen_img_height"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/item_gridView_txtView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:textAlignment="center"
        android:textColor="@color/black"
        android:textSize="16sp" />

</RelativeLayout>

3. Now make the adapter named MyCustomGridViewAdapter.java for setting the views in your gridView as follows:

public class MyCustomGridViewAdapter extends BaseAdapter {

    private Context mContext;
    private final ArrayList<YOUR_CLASS_TYPE> arrayList;

    public MyCustomGridViewAdapter(Context context, ArrayList<YOUR_CLASS_TYPE> arrayList) {
        this.mContext = context;
        this.arrayList = arrayList;
    }

    @Override
    public int getCount() {
        return arrayList.size();
    }

    @Override
    public YOUR_CLASS_TYPE getItem(int position) {
        return arrayList.get(position);
    }

    @Override
    public long getItemId(int arg0) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.gridview_item, parent, false);
            holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.item_gridView_txtView);
            holder.imageView = (ImageView) convertView.findViewById(R.id.item_gridView_imgView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.text.setText(arrayList.get(position).getName());
  // Glide is the library used to laod an image into an imageView
        Glide.with(mContext).load(arrayList.get(position).getThumbnail_url())
                .asBitmap().centerCrop()
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .into(holder.imageView);
        return convertView;
    }

    private static class ViewHolder {
        TextView text;
        ImageView imageView;
    }
}

4. Now create a Fragment named ExpandableListViewFragment.java and add the following code snippet:

public class ExpandableListViewFragment extends Fragment implements GridViewItemClickInterface {

    private List<String> listDataHeader;
    private HashMap<String, ArrayList<YOUR_CLASS_TYPE>> listDataChild;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.layout_fragment_expandable_listvw, container, false);

        ExpandableListView mExpandablelistView = (ExpandableListView) rootView.findViewById(R.id.fragment_expandable_listview);
        prepareListData();
        CustomExpandableListAdapter mExpandableListAdapter = new CustomExpandableListAdapter(getActivity(), listDataHeader, listDataChild, this);
        mExpandablelistView.setAdapter(mExpandableListAdapter);
        mExpandablelistView.setOnGroupClickListener(new OnGroupClickListener() {
         @Override
         public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
                // This way the expander cannot be collapsed
                // on click event of group item
                return false;
            }
        });
        return rootView;
    }

    private void prepareListData() {
        listDataHeader = new ArrayList<>();
        listDataHeader.add("Folder 1");
        listDataHeader.add("Folder 2");
        listDataChild = new HashMap<>();
        ArrayList<YOUR_CLASS_TYPE> details = new ArrayList<YOUR_CLASS_TYPE>();// fill the data as per your requirements
        listDataChild.put(listDataHeader.get(0), details);
        listDataChild.put(listDataHeader.get(1), null); // just to show null item view
    }

    @Override
    public void onGridViewItemClick(YOUR_CLASS_TYPE item) {
        Toast.makeText(getActivity(), "Currently selected is: " + item.getName(), Toast.LENGTH_SHORT).show();
    }
}

5. The layout file for the above fragment is as follows: layout_fragment_expandable_listvw.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">

        <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ExpandableListView
            android:id="@+id/fragment_expandable_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@color/transparent"
            android:listSelector="@color/transparent" />

    </RelativeLayout>

</LinearLayout>

6. Now create the adapter named CustomExpandableListAdapter.java file with the following code snippet:

public class CustomExpandableListAdapter extends BaseExpandableListAdapter {

    private Context mContext;
    private List<String> mListDataHeader;
    private HashMap<String, ArrayList<YOUR_CLASS_TYPE>> mListDataChild;
    private LayoutInflater mInflater;
    private GridViewItemClickInterface gridViewItemClickListener;
    private int columnsCount = 3;

    public CustomExpandableListAdapter(Context context, List<String> listDataHeader, HashMap<String, ArrayList<YOUR_CLASS_TYPE>> listChildData, GridViewItemClickInterface listener) {
        this.mContext = context;
        this.mListDataHeader = listDataHeader;
        this.mListDataChild = listChildData;
        this.mInflater = LayoutInflater.from(context);
        this.gridViewItemClickListener = listener;
    }

    @Override
    public int getGroupCount() {
        return mListDataHeader.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return 1;
    }

    @Override
    public Object getGroup(int groupPosition) {
        return mListDataHeader.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return mListDataChild.get(this.mListDataHeader.get(groupPosition))
                .size();
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        String headerTitle = (String) getGroup(groupPosition);
        if (convertView == null) {
            // layout view of header group
            convertView = mInflater.inflate(R.layout.expandable_listvw_header, null);
        }
        TextView headerLabel = (TextView) convertView.findViewById(R.id.txtView_header);
        headerLabel.setTypeface(null, Typeface.BOLD);
        headerLabel.setText(headerTitle);
        return convertView;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        convertView = mInflater.inflate(R.layout.expandable_listvw_item, null);
        final ArrayList<YOUR_CLASS_TYPE> items = mListDataChild.get(mListDataHeader.get(groupPosition));
        if (items.size() > 0) {
            MyCustomGridView gridView = (MyCustomGridView) convertView.findViewById(R.id.item_custom_gridView);
            gridView.setNumColumns(columnsCount);
            gridView.setVerticalSpacing(10);
            gridView.setHorizontalSpacing(10);
            MyCustomGridViewAdapter adapter = new MyCustomGridViewAdapter(mContext, items);
            gridView.setAdapter(adapter);
            int totalHeight = 0;
// This is to get the actual size of gridView at runtime while filling the items into it
            for (int size = 0; size < adapter.getCount(); size++) {
                RelativeLayout relativeLayout = (RelativeLayout) adapter.getView(size, null, gridView);
                relativeLayout.measure(0, 0);
                totalHeight += relativeLayout.getMeasuredHeight();
            }
            gridView.setGridViewItemHeight(v);
            gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    gridViewItemClickListener.onGridViewItemClick(items.get(position));
                }
            });
        } else {
            convertView = mInflater.inflate(R.layout.layout_blank, null);
        }

        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

}

7. The layout files for ExpandableListView group and item are as follows:
a) expandable_listvw_header.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="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txtView_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/colorBlack"
        android:textSize="22sp" />

</LinearLayout>

b) expandable_listvw_item.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">

    <com.nishant.sample.ExpandableListPac.MyCustomGridView
        android:id="@+id/item_custom_gridView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

c) layout_blank.xml - This is the layout to be shown when no items are available.

<?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">

    <TextView
        android:id="@+id/item_txt_no_items"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:gravity="center_horizontal|center_vertical"
        android:text="No items available"
        android:textSize="22sp" />

</LinearLayout>

8. The interface class GridViewItemClickInterface.java is as follows:

public interface GridViewItemClickInterface {

    void onGridViewItemClick(YOUR_CLASS_TYPE item);
}

That's all. Now run your project and enjoy.

Share and comment if any issues.

Sunday, 21 May 2017

Android: Layout as background of view

In this post I will show you how to set your custom layout as the background of your item of your listview or gridview or any other view that contains long list of data, through your adapter.
This is very simple and can be done as follows:

1. Make a file custom_placeholder.xml of your custom layout as follows:

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="6dp"
        android:background="@drawable/shape_bg">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="Downloading..."
            android:textAlignment="center"
            android:textSize="14sp" />

    </RelativeLayout>

</LinearLayout>


2. Now in the getView method of your adapter just replace your code with below code:

public View getView(final int position, View convertView, ViewGroup parent) {
        CustomViewHolder mViewHolder;
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = layoutInflater.inflate(R.layout.item_gridview_assets, parent, false);
            mViewHolder = new CustomViewHolder(convertView);  // set your layout (convertView) as your viewHolder
            convertView.setTag(mViewHolder);
        } else {
            mViewHolder = (CustomViewHolder) convertView.getTag();
        }
          //Now check the condition whether your image is available or not  
        String value = imageList.get(position);
        if (!value.equalsIgnoreCase("")) {
             // case when image is available  
           mViewHolder.imgAssets.setBackgroundResource(R.drawable.sample_img);
        } else {
             // case when image is not available  
           // This will inflate your custom view that you created above
            LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = layoutInflater.inflate(R.layout.custom_placeholder, parent, false);
        }
        return convertView;
    }

That's all.
Share and comment if any issues.