Android ListView With Header | Pinned header, Sticky, Section

android listview sticky header like iphone, android recyclerview sticky header like iphone, android listview with header and footer, android recyclerview with header and footer

The article of today is about Android ListView With Header And Footer Example.

Different ListView Headers

1. Android ListView With Fixed Header And Footer Example

2. Android ListView Sticky Header Like iPhone | Pinned header listview

3. Android Multi-Column ListView With Sticky Header Example

4. Android ListView Section Header Example Group By Header

1. Android ListView With Fixed Header And Footer Example

We can add fixed header and footer to listview which are always visible.

Android have built in methods like addheaderview and addfooterview for ListView class.

We can use these methods to make custom header and footer for listview.

Header and Footer will always stick and visible at the start (Header) and end (Footer) of the listview.

Example of this tutorial can be the checkout or bill summary screen of any restaurant.

This checkout screen need the header and footer of the listview.

Here, different food items with their name,quantity and price are set in the listview because every order have different number of food items so it is not fix.

And, in the header of the listview, you need to fix the name and logo of the restaurant while in footer, you can show the total amount payable.

Having built in methods is great advantage and simplifies the whole process.

Look Header And Footer

Step 1. Separate Views

We need to create separate views for header and footer in this example.

Separate views gives us better options for customization.

First of all create a new XML resource file named header_view.xml

Add the following coding lines in header_view.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">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:id="@+id/htv"
        android:padding="10dp"
        android:textColor="#fff"
        android:textStyle="bold"
        android:gravity="center"
        android:textSize="20dp"
        android:text="I am Header View of ListView. Add ImageView, Button etc widget here."
        android:background="#3a0ae7"/>

</LinearLayout>

I have taken only one textview in the header file.

You can add imageview to add image, button, videoview or any other UI widget as you like.

Customize this file with different colors and styles and images to make it more attractive or as per your client’s requirements.

Make another XML layout resource file in res->layout directory.

Name of this file should be footer_view.xml

Copy the below source code in footer_view.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">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:id="@+id/ftv"
        android:padding="10dp"
        android:textColor="#fff"
        android:textStyle="bold"
        android:gravity="center"
        android:textSize="20dp"
        android:text="I am Footer View of Listview. Add ImageView, Button etc widget here."
        android:background="#e7660a"/>

</LinearLayout>

This file will create the view for footer.

I have make it same as the header view with just one textview.

You can also customize this file with various UI widgets and styles.

Step 3. XML file For ListView

Now we will make a listview.

  • For this, we need to make one layout file which will represent the each row layout of the listview.

Prepare a new XML file named listview_item.xml and add the following lines

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:id="@+id/tv"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:paddingLeft="10dp"
        android:textColor="#fff"
        android:textStyle="bold"
        android:gravity="center_vertical"
        android:background="@color/colorAccent"
        android:text="111"
        android:textSize="20sp"/>

</LinearLayout>

Again, only one textview with some properties like, textcolor, textsize, textstyle, background color etc.

All the rows of the listview will get the view like above file.

Step 5. List Adapter

Every listview require adapter class for data management.

Make a new JAVA class and set the name ListAdapter.java

Code structure for ListAdapter.java is as the following

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ListAdapter extends BaseAdapter {

    private Context context;
    private int digit[] = new int[] {};

    public ListAdapter (Context context, int digit[]) {

        this.context = context;
        this.digit = digit;
    }

    @Override
    public int getViewTypeCount() {
        return getCount();
    }
    @Override
    public int getItemViewType(int position) {

        return position;
    }

    @Override
    public int getCount() {
        return digit.length;
    }

    @Override
    public Object getItem(int position) {
        return digit[position];
    }

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

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.listview_item, null, true);

            holder.tvProduct = (TextView) convertView.findViewById(R.id.tv);

            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvProduct.setText("Item Number : "+ digit[position]);

        return convertView;
    }

    private class ViewHolder {

        protected TextView tvProduct;

    }

}

Starring at adapter

Read the below lines

 private Context context;
    private int digit[] = new int[] {};

    public ListAdapter (Context context, int digit[]) {

        this.context = context;
        this.digit = digit;
    }

Compiler will first make context and integer array.

Then there is constructor which has two parameters.

We will initialize our objects for context and integer array using these parameters.

Now consider the following method

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.listview_item, null, true);

            holder.tvProduct = (TextView) convertView.findViewById(R.id.tv);

            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvProduct.setText("Item Number : "+ digit[position]);

        return convertView;
    }

Using this method, compiler will first inflate the layout file listview_item.xml 

Now every children of listview will get the user interface developed by the  listview_item.xml file.

After this, compiler will set the value of the textview.

Compiler will use integer array to populate the values in the textview.

Step 6. Final Updates

At last, we need to write some code in activity_main.xml and MainActivity.java files.

Source code for activity_main.xml looks like the below

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="20dp"
        android:id="@+id/listView">

    </ListView>
</android.support.constraint.ConstraintLayout>

Just one listview and nothing else in this file.

Code block for MainActivity.java is as the below

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private int digit[] = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18};
    private ListAdapter listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);

        LayoutInflater inflater = (LayoutInflater) this.getSystemService(this.LAYOUT_INFLATER_SERVICE);

        //Add Header View
        View headerView = inflater.inflate(R.layout.header_view, null, false);
        listView.addHeaderView(headerView);//Add view to list view as header view

        //Add Footer View
        View footerView = inflater.inflate(R.layout.footer_view, null, false);
        listView.addFooterView(footerView);//Add view to list view as footer view

        listAdapter = new ListAdapter(this,digit);
        listView.setAdapter(listAdapter);

    }
}

Noticing the above

Consider the below source code

 private ListView listView;
 private int digit[] = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18};
 private ListAdapter listAdapter;

First line is giving us the object of the Listview.

Second is defining one integer array. This integer holds the values from 1 to 18.

Third line is making an object of the ListAdapter class.

Now see the following coding lines

LayoutInflater inflater = (LayoutInflater) this.getSystemService(this.LAYOUT_INFLATER_SERVICE);

        //Add Header View
        View headerView = inflater.inflate(R.layout.header_view, null, false);
        listView.addHeaderView(headerView);//Add view to list view as header view

First, compiler will create an object of the LayoutInflater class.

Using this inflater, compiler has inflate the header_view.xml file in the second line.

Third line is important. We are setting up the header file in this line using addheaderView() method.

Attend the following code block

 //Add Footer View
        View footerView = inflater.inflate(R.layout.footer_view, null, false);
        listView.addFooterView(footerView);//Add view to list view as footer view

First line will inflate the footer_view to initialize the object (footerView) of the View class.

Then compiler will stick the footer to the listview using addFooterView() method.

 listAdapter = new ListAdapter(this,digit);
 listView.setAdapter(listAdapter);

Finally, above code will set the adapter into listview and our listview is ready with sticky header and footer.





2. Android ListView Sticky Header Like iphone | Pinned header listview

We will develop Android ListView Sticky Header Like iphone example in this tutorial.

Sometimes you want to make pinned header listview in android app.

This example will guide you to make sticky or pinned header for listview in android studio.

Pinned header means that a header which have certain number of child rows and this header sticks at the top of the ListView.

Then, again there will be another sticky header which will have certain number of children and it replaces old header.

You may have notice this type of listview in iphone or ios.

Android does not have any built in method or class to achieve this type of user interface for listview.

We need to customize out traditional listview and adapter class for this purpose.

Last Views For Header

Step 1. Adding Colors

I will separate the headers and children with different colors.

Also, different headers may have different colors.

So let us add some colors in res->values->colors.xml file.

Write the following lines in res->values->colors.xml file.

 <color name="holo_blue_light">#ff33b5e5</color>
    <color name="holo_green_light">#ff99cc00</color>
    <color name="holo_red_light">#ffff4444</color>
    <color name="holo_orange_light">#ffffbb33</color>

I have added blue, green, red and orange colors.

You can add many more colors as per your requirements.

Step 2. Adding Image

In this example we need to add one image in the drawable folder.

Download this image by clicking the below link

[sociallocker]Download scrollbarlight Image[/sociallocker]

After downloading the image, add it into res->drawable directory.

We need this image in HeaderListView class which we will create in the next step.

Step 3. Preparing HeaderListView And Section Adapter Class

Create a new JAVA class and give it a name HeaderListView.java

Copy the below code block in HeaderListView.java class

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.RelativeLayout;

public class HeaderListView extends RelativeLayout {

    // TODO: Handle listViews with fast scroll
    // TODO: See if there are methods to dispatch to mListView

    private static final int FADE_DELAY    = 1000;
    private static final int FADE_DURATION = 2000;

    private InternalListView mListView;
    private SectionAdapter   mAdapter;
    private RelativeLayout   mHeader;
    private View             mHeaderConvertView;
    private FrameLayout      mScrollView;
    private AbsListView.OnScrollListener mExternalOnScrollListener;

    public HeaderListView(Context context) {
        super(context);
        init(context, null);
    }

    public HeaderListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mListView = new InternalListView(getContext(), attrs);
        LayoutParams listParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        listParams.addRule(ALIGN_PARENT_TOP);
        mListView.setLayoutParams(listParams);
        mListView.setOnScrollListener(new HeaderListViewOnScrollListener());
        mListView.setVerticalScrollBarEnabled(false);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mAdapter != null) {
                    mAdapter.onItemClick(parent, view, position, id);
                }
            }
        });
        addView(mListView);

        mHeader = new RelativeLayout(getContext());
        LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        headerParams.addRule(ALIGN_PARENT_TOP);
        mHeader.setLayoutParams(headerParams);
        mHeader.setGravity(Gravity.BOTTOM);
        addView(mHeader);

        // The list view's scroll bar can be hidden by the header, so we display our own scroll bar instead
        Drawable scrollBarDrawable = getResources().getDrawable(R.drawable.scrollbar_handle_holo_light);
        mScrollView = new FrameLayout(getContext());
        LayoutParams scrollParams = new LayoutParams(scrollBarDrawable.getIntrinsicWidth(), LayoutParams.MATCH_PARENT);
        scrollParams.addRule(ALIGN_PARENT_RIGHT);
        scrollParams.rightMargin = (int) dpToPx(2);
        mScrollView.setLayoutParams(scrollParams);

        ImageView scrollIndicator = new ImageView(context);
        scrollIndicator.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        scrollIndicator.setImageDrawable(scrollBarDrawable);
        scrollIndicator.setScaleType(ScaleType.FIT_XY);
        mScrollView.addView(scrollIndicator);
        mScrollView.setVisibility(INVISIBLE);

        addView(mScrollView);
    }

    public void setAdapter(SectionAdapter adapter) {
        mAdapter = adapter;
        mListView.setAdapter(adapter);
    }

    public void setOnScrollListener(AbsListView.OnScrollListener l) {
        mExternalOnScrollListener = l;
    }

    private class HeaderListViewOnScrollListener implements AbsListView.OnScrollListener {

        private int            previousFirstVisibleItem = -1;
        private int            direction                = 0;
        private int            actualSection            = 0;
        private boolean        scrollingStart           = false;
        private boolean        doneMeasuring            = false;
        private int            lastResetSection         = -1;
        private int            nextH;
        private int            prevH;
        private View           previous;
        private View           next;
        private AlphaAnimation fadeOut                  = new AlphaAnimation(1f, 0f);
        private boolean        noHeaderUpToHeader       = false;
        private boolean        didScroll = false;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (mExternalOnScrollListener != null) {
                mExternalOnScrollListener.onScrollStateChanged(view, scrollState);
            }
            didScroll = true;
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            if (mExternalOnScrollListener != null) {
                mExternalOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

            if (!didScroll) {
                return;
            }

            firstVisibleItem -= mListView.getHeaderViewsCount();
            if (firstVisibleItem < 0) {
                mHeader.removeAllViews();
                return;
            }

            updateScrollBar();
            if (visibleItemCount > 0 && firstVisibleItem == 0 && mHeader.getChildAt(0) == null) {
                addSectionHeader(0);
                lastResetSection = 0;
            }

            int realFirstVisibleItem = getRealFirstVisibleItem(firstVisibleItem, visibleItemCount);
            if (totalItemCount > 0 && previousFirstVisibleItem != realFirstVisibleItem) {
                direction = realFirstVisibleItem - previousFirstVisibleItem;

                actualSection = mAdapter.getSection(realFirstVisibleItem);

                boolean currIsHeader = mAdapter.isSectionHeader(realFirstVisibleItem);
                boolean prevHasHeader = mAdapter.hasSectionHeaderView(actualSection - 1);
                boolean nextHasHeader = mAdapter.hasSectionHeaderView(actualSection + 1);
                boolean currHasHeader = mAdapter.hasSectionHeaderView(actualSection);
                boolean currIsLast = mAdapter.getRowInSection(realFirstVisibleItem) == mAdapter.numberOfRows(actualSection) - 1;
                boolean prevHasRows = mAdapter.numberOfRows(actualSection - 1) > 0;
                boolean currIsFirst = mAdapter.getRowInSection(realFirstVisibleItem) == 0;

                boolean needScrolling = currIsFirst && !currHasHeader && prevHasHeader && realFirstVisibleItem != firstVisibleItem;
                boolean needNoHeaderUpToHeader = currIsLast && currHasHeader && !nextHasHeader && realFirstVisibleItem == firstVisibleItem && Math.abs(mListView.getChildAt(0).getTop()) >= mListView.getChildAt(0).getHeight() / 2;
                
                noHeaderUpToHeader = false;
                if (currIsHeader && !prevHasHeader && firstVisibleItem >= 0) {
                    resetHeader(direction < 0 ? actualSection - 1 : actualSection);
                } else if ((currIsHeader && firstVisibleItem > 0) || needScrolling) {
                    if (!prevHasRows) {
                        resetHeader(actualSection-1);
                    }
                    startScrolling();
                } else if (needNoHeaderUpToHeader) {
                    noHeaderUpToHeader = true;
                } else if (lastResetSection != actualSection) {
                    resetHeader(actualSection);
                }

                previousFirstVisibleItem = realFirstVisibleItem;
            }

            if (scrollingStart) {
                int scrolled = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getTop() : 0;

                if (!doneMeasuring) {
                    setMeasurements(realFirstVisibleItem, firstVisibleItem);
                }

                int headerH = doneMeasuring ? (prevH - nextH) * direction * Math.abs(scrolled) / (direction < 0 ? nextH : prevH) + (direction > 0 ? nextH : prevH) : 0;

                mHeader.scrollTo(0, -Math.min(0, scrolled - headerH));
                if (doneMeasuring && headerH != mHeader.getLayoutParams().height) {
                    LayoutParams p = (LayoutParams) (direction < 0 ? next.getLayoutParams() : previous.getLayoutParams());
                    p.topMargin = headerH - p.height;
                    mHeader.getLayoutParams().height = headerH;
                    mHeader.requestLayout();
                }
            }

            if (noHeaderUpToHeader) {
                if (lastResetSection != actualSection) {
                    addSectionHeader(actualSection);
                    lastResetSection = actualSection + 1;
                }
                mHeader.scrollTo(0, mHeader.getLayoutParams().height - (mListView.getChildAt(0).getHeight() + mListView.getChildAt(0).getTop()));
            }
        }

        private void startScrolling() {
            scrollingStart = true;
            doneMeasuring = false;
            lastResetSection = -1;
        }

        private void resetHeader(int section) {
            scrollingStart = false;
            addSectionHeader(section);
            mHeader.requestLayout();
            lastResetSection = section;
        }

        private void setMeasurements(int realFirstVisibleItem, int firstVisibleItem) {

            if (direction > 0) {
                nextH = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getMeasuredHeight() : 0;
            }

            previous = mHeader.getChildAt(0);
            prevH = previous != null ? previous.getMeasuredHeight() : mHeader.getHeight();

            if (direction < 0) {
                if (lastResetSection != actualSection - 1) {
                    addSectionHeader(Math.max(0, actualSection - 1));
                    next = mHeader.getChildAt(0);
                }
                nextH = mHeader.getChildCount() > 0 ? mHeader.getChildAt(0).getMeasuredHeight() : 0;
                mHeader.scrollTo(0, prevH);
            }
            doneMeasuring = previous != null && prevH > 0 && nextH > 0;
        }

        private void updateScrollBar() {
            if (mHeader != null && mListView != null && mScrollView != null) {
                int offset = mListView.computeVerticalScrollOffset();
                int range = mListView.computeVerticalScrollRange();
                int extent = mListView.computeVerticalScrollExtent();
                mScrollView.setVisibility(extent >= range ? View.INVISIBLE : View.VISIBLE);
                if (extent >= range) {
                    return;
                }
                int top = range == 0 ? mListView.getHeight() : mListView.getHeight() * offset / range;
                int bottom = range == 0 ? 0 : mListView.getHeight() - mListView.getHeight() * (offset + extent) / range;
                mScrollView.setPadding(0, top, 0, bottom);
                fadeOut.reset();
                fadeOut.setFillBefore(true);
                fadeOut.setFillAfter(true);
                fadeOut.setStartOffset(FADE_DELAY);
                fadeOut.setDuration(FADE_DURATION);
                mScrollView.clearAnimation();
                mScrollView.startAnimation(fadeOut);
            }
        }

        private void addSectionHeader(int actualSection) {
            View previousHeader = mHeader.getChildAt(0);
            if (previousHeader != null) {
                mHeader.removeViewAt(0);
            }

            if (mAdapter.hasSectionHeaderView(actualSection)) {
                mHeaderConvertView = mAdapter.getSectionHeaderView(actualSection, mHeaderConvertView, mHeader);
                mHeaderConvertView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

                mHeaderConvertView.measure(MeasureSpec.makeMeasureSpec(mHeader.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

                mHeader.getLayoutParams().height = mHeaderConvertView.getMeasuredHeight();
                mHeaderConvertView.scrollTo(0, 0);
                mHeader.scrollTo(0, 0);
                mHeader.addView(mHeaderConvertView, 0);
            } else {
                mHeader.getLayoutParams().height = 0;
                mHeader.scrollTo(0, 0);
            }

            mScrollView.bringToFront();
        }

        private int getRealFirstVisibleItem(int firstVisibleItem, int visibleItemCount) {
            if (visibleItemCount == 0) {
                return -1;
            }
            int relativeIndex = 0, totalHeight = mListView.getChildAt(0).getTop();
            for (relativeIndex = 0; relativeIndex < visibleItemCount && totalHeight < mHeader.getHeight(); relativeIndex++) {
                totalHeight += mListView.getChildAt(relativeIndex).getHeight();
            }
            int realFVI = Math.max(firstVisibleItem, firstVisibleItem + relativeIndex - 1);
            return realFVI;
        }
    }

    public ListView getListView() {
        return mListView;
    }
    
    public void addHeaderView(View v) {
        mListView.addHeaderView(v);
    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
    }

    protected class InternalListView extends ListView {

        public InternalListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected int computeVerticalScrollExtent() {
            return super.computeVerticalScrollExtent();
        }

        @Override
        protected int computeVerticalScrollOffset() {
            return super.computeVerticalScrollOffset();
        }

        @Override
        protected int computeVerticalScrollRange() {
            return super.computeVerticalScrollRange();
        }
    }
}

This class helps us to make a special listview which can support the sticky header.

Above class is the transformation from traditional listview to our desired listview.

Now make another new JAVA class named SectionAdapter.java

Write down the following Coding lines in SectionAdapter.java

import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;

public abstract class SectionAdapter extends BaseAdapter implements OnItemClickListener {

    private int mCount = -1;

    public abstract int numberOfSections();

    public abstract int numberOfRows(int section);

    public abstract View getRowView(int section, int row, View convertView, ViewGroup parent);

    public abstract Object getRowItem(int section, int row);

    public boolean hasSectionHeaderView(int section) {
        return false;
    }

    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
        return null;
    }

    public Object getSectionHeaderItem(int section) {
        return null;
    }

    public int getRowViewTypeCount() {
        return 1;
    }

    public int getSectionHeaderViewTypeCount() {
        return 1;
    }

    /**
     * Must return a value between 0 and getRowViewTypeCount() (excluded)
     */
    public int getRowItemViewType(int section, int row) {
        return 0;
    }

    /**
     * Must return a value between 0 and getSectionHeaderViewTypeCount() (excluded, if > 0)
     */
    public int getSectionHeaderItemViewType(int section) {
        return 0;
    }

    @Override
    /**
     * Dispatched to call onRowItemClick
     */
    public final void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        onRowItemClick(parent, view, getSection(position), getRowInSection(position), id);
    }

    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {

    }

    @Override
    /**
     * Counts the amount of cells = headers + rows
     */
    public final int getCount() {
        if (mCount < 0) {
            mCount = numberOfCellsBeforeSection(numberOfSections());
        }
        return mCount;
    }

    @Override
    public boolean isEmpty() {
        return getCount() == 0;
    }

    @Override
    /**
     * Dispatched to call getRowItem or getSectionHeaderItem
     */
    public final Object getItem(int position) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            if (hasSectionHeaderView(section)) {
                return getSectionHeaderItem(section);
            }
            return null;
        }
        return getRowItem(section, getRowInSection(position));
    }

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

    @Override
    /**
     * Dispatched to call getRowView or getSectionHeaderView
     */
    public final View getView(int position, View convertView, ViewGroup parent) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            if (hasSectionHeaderView(section)) {
                return getSectionHeaderView(section, convertView, parent);
            }
            return null;
        }
        return getRowView(section, getRowInSection(position), convertView, parent);
    }

    /**
     * Returns the section number of the indicated cell
     */
    protected int getSection(int position) {
        int section = 0;
        int cellCounter = 0;
        while (cellCounter <= position && section <= numberOfSections()) {
            cellCounter += numberOfCellsInSection(section);
            section++;
        }
        return section - 1;
    }

    /**
     * Returns the row index of the indicated cell Should not be call with
     * positions directing to section headers
     */
    protected int getRowInSection(int position) {
        int section = getSection(position);
        int row = position - numberOfCellsBeforeSection(section);
        if (hasSectionHeaderView(section)) {
            return row - 1;
        } else {
            return row;
        }
    }

    /**
     * Returns true if the cell at this index is a section header
     */
    protected boolean isSectionHeader(int position) {
        int section = getSection(position);
        return hasSectionHeaderView(section) && numberOfCellsBeforeSection(section) == position;
    }

    /**
     * Returns the number of cells (= headers + rows) before the indicated
     * section
     */
    protected int numberOfCellsBeforeSection(int section) {
        int count = 0;
        for (int i = 0; i < Math.min(numberOfSections(), section); i++) {
            count += numberOfCellsInSection(i);
        }
        return count;
    }

    private int numberOfCellsInSection(int section) {
        return numberOfRows(section) + (hasSectionHeaderView(section) ? 1 : 0);
    }

    @Override
    public void notifyDataSetChanged() {
        mCount = numberOfCellsBeforeSection(numberOfSections());
        super.notifyDataSetChanged();
    }

    @Override
    public void notifyDataSetInvalidated() {
        mCount = numberOfCellsBeforeSection(numberOfSections());
        super.notifyDataSetInvalidated();
    }

    @Override
    /**
     * Dispatched to call getRowItemViewType or getSectionHeaderItemViewType
     */
    public final int getItemViewType(int position) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            return getRowViewTypeCount() + getSectionHeaderItemViewType(section);
        } else {
            return getRowItemViewType(section, getRowInSection(position));
        }
    }

    @Override
    /**
     * Dispatched to call getRowViewTypeCount and getSectionHeaderViewTypeCount
     */
    public final int getViewTypeCount() {
        return getRowViewTypeCount() + getSectionHeaderViewTypeCount();
    }

    @Override
    /**
     * By default, disables section headers
     */
    public boolean isEnabled(int position) {
        return (disableHeaders() || !isSectionHeader(position)) && isRowEnabled(getSection(position), getRowInSection(position));
    }

    public boolean disableHeaders() {
        return false;
    }

    public boolean isRowEnabled(int section, int row) {
        return true;
    }
}

For special listview, we also need to have special adapter with it.

Above class is making that special adapter for our special listview.

We will extend this adapter while creating the actual adapter for our listview.

Step 4. Ios Adapter

Let us create an adapter what will populate out listview with pinned header.

Make a new class and give it a name IosAdapter.java

Following is the code structure for IosAdapter.java class

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;

public class IosAdapter extends SectionAdapter {

    private Context context;

    public IosAdapter(Context context) {

        this.context = context;
    }

    @Override
    public int numberOfSections() {
        return 4; //return the total number of headers
    }

    @Override
    public int numberOfRows(int section) {
        return 5; //return the number of children for particular header
    }

    @Override
    public Object getRowItem(int section, int row) {
        return null;
    }

    @Override
    public boolean hasSectionHeaderView(int section) {
        return true;
    }

    @Override
    public View getRowView(int section, int row, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (convertView == null) {
            convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
        }
        ((TextView) convertView).setText("Section " + section + " Row " + row);
        return convertView;
    }

    @Override
    public int getSectionHeaderViewTypeCount() {
        return 2;
    }

    @Override
    public int getSectionHeaderItemViewType(int section) {
        return section % 2;
    }

    @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            if (getSectionHeaderItemViewType(section) == 0) {
                convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
            } else {
                convertView = inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_2), null);
            }
        }

        if (getSectionHeaderItemViewType(section) == 0) {
            ((TextView) convertView).setText("Header for section " + section);
        } else {
            ((TextView) convertView.findViewById(android.R.id.text1)).setText("Header for section " + section);
            //  ((TextView) convertView.findViewById(android.R.id.text2)).setText("Has a detail text field");
        }

        switch (section) {
            case 0:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_red_light));
                break;
            case 1:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_light));
                break;
            case 2:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_green_light));
                break;
            case 3:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_blue_light));
                break;
        }
        return convertView;
    }

    @Override
    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {
        super.onRowItemClick(parent, view, section, row, id);
        Toast.makeText(context, "Section " + section + " Row " + row, Toast.LENGTH_SHORT).show();
    }

}

Looking Deeply

Let us look what the above class IosAdapter will do actually.

Consider below code

 @Override
    public int numberOfSections() {
        return 4; //return the total number of headers
    }
  • This method will return the total number of sticky or pinned headers.

Look the following code

 @Override
    public int numberOfRows(int section) {
        return 5; //return the number of children for particular header
    }

Above method is returning the total number of child for ant specific header.

Number of header is at the parameter (int section)

Above method will return 5 as the number of children for header at the position (int section)

getRowView()

code for above method

 @Override
    public View getRowView(int section, int row, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (convertView == null) {
            convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
        }
        ((TextView) convertView).setText("Section " + section + " Row " + row);
        return convertView;
    }

Above method will generate the view for the children items.

We will also set the text for children in this method.

Address of child can be like a child of the header at -> position section and a child position for that header is -> int row

Following method getSectionHeaderView() is making a layout for header

  @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            if (getSectionHeaderItemViewType(section) == 0) {
                convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
            } else {
                convertView = inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_2), null);
            }
        }

        if (getSectionHeaderItemViewType(section) == 0) {
            ((TextView) convertView).setText("Header for section " + section);
        } else {
            ((TextView) convertView.findViewById(android.R.id.text1)).setText("Header for section " + section);
            //  ((TextView) convertView.findViewById(android.R.id.text2)).setText("Has a detail text field");
        }

        switch (section) {
            case 0:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_red_light));
                break;
            case 1:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_light));
                break;
            case 2:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_green_light));
                break;
            case 3:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_blue_light));
                break;
        }
        return convertView;
    }

First parameter of getSectionHeaderView() method is the position (int section) for header.

We will set the name of the header in this method.

I have also set the background colors in this method using switch statement.

Handling Click Events

To handle the click events of the listview, review the below method

 @Override
    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {
        super.onRowItemClick(parent, view, section, row, id);
        Toast.makeText(context, "Section " + section + " Row " + row, Toast.LENGTH_SHORT).show();
    }

You can have both header and row position in this click event.

Step 5. Main Updates

Now last thing is to update activity_main.xml and MainActivity.java files.

Write the following coding lines in activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/lv">

    </com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView>

</android.support.constraint.ConstraintLayout>

As you can see in the above code, I have used HeaderListView class to define listview.

It is referencing that HeaderListView which we have created in the step 3.

Here, in this line <com.example.parsa………ios>, you need to replace it with the package name of your android studio code.

You can find your package name in AndroidManifest.xml file.

Now make following source code in MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private IosAdapter iosAdapter;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HeaderListView headerListView = findViewById(R.id.lv);

        iosAdapter = new IosAdapter(this);
        headerListView.setAdapter(iosAdapter);

    }
}

In this main class, we just need to set up the listview with proper adapter.

We will set the object of IosAdapter class to the listview.





3. Android Multi Column ListView With Sticky Header Example

Android Multi Column ListView With Sticky Header Example is today’s shining topic.

Multi column listview means that every row of the listview have more than one column and this listview is developing a table like structure.

You need to create a ListView with multiple columns in many applications to show multiple data.

Sometimes you want to have a static headers and just below that header, you want multi column listview.

For example, to show a order summary of any restaurant, you have two sticky headers like name of the restaurant and name of the customer.

Just after these two headers, your listview starts where every row includes columns like name of product, price of product, quantity of product etc.

We will develop just this kind of example in this article.

See Our MultiColumn ListView

Step 1. Making a List Item File

For creating a ListView, we need to make separate xml layout file.

This layout file represents the whole view structure of each listview row item.

So make a new file named list_item.xml in res->layout structure.

Add the below source code in list_item.xml file.

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#16a307"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:id="@+id/tvProduct"
            android:textColor="#fff"
            android:textSize="20sp"
            android:gravity="center"
            android:layout_weight="2"
            android:text="Product Name"/>

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/tvQty"
            android:textColor="#fff"
            android:textSize="20sp"
            android:gravity="center"
            android:layout_weight="1"
            android:text="Quantity"/>

    </LinearLayout>

</LinearLayout>
  • In listview, we have two columns in every row item. One column is showing product name and another is showing the quantity of the product.
  • In the above list_item.xml file, two textviews are representing these two columns.
  • We will use this list_item.xml file in the adapter class where compiler will inflate it in every iteration while making each row for listview.

Step 2. Food Model

The proper way to make a reliable listview in every scenario is to make a listview with help of the model class.

Model class includes getter and setter methods for data maintenance.

For example, in our example we have data in the form of product name and it’s quantity.

We can store product name in String and quantity in integer format.

Create a new java class named FoodModel.java in your app.

Following is the coding lines for FoodModel.java class

public class FoodModel {

    private String product;
    private int qty;

    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }

    public int getQty() {
        return qty;
    }

    public void setQty(int qty) {
        this.qty = qty;
    }
}
  • As I have said earlier, above model class includes get and set method for both product name and quantity.

Step 3. Food Adapter Class

Now let us create a food adapter class which will generate data set for the listview.

Prepare a new java class and set it’s name as FoodAdapter.java

Write down the following lines in FoodAdapter.java class

public class FoodAdapter extends BaseAdapter {

    private Context context;
    private ArrayList<FoodModel> foodModelArrayList;

    public FoodAdapter(Context context, ArrayList<FoodModel> foodModelArrayList) {

        this.context = context;
        this.foodModelArrayList = foodModelArrayList;
    }

    @Override
    public int getViewTypeCount() {
        return getCount();
    }
    @Override
    public int getItemViewType(int position) {

        return position;
    }

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

    @Override
    public Object getItem(int position) {
        return foodModelArrayList.get(position);
    }

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

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_item, null, true);

            holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
            holder.tvQty = (TextView) convertView.findViewById(R.id.tvQty);

            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvProduct.setText(foodModelArrayList.get(position).getProduct());
        holder.tvQty.setText(String.valueOf(foodModelArrayList.get(position).getQty()));

        return convertView;
    }

    private class ViewHolder {

        protected TextView tvProduct, tvQty;

    }

}

Diving Step by Step

Hang on, we will now understand above code one by one snippets.

Consider the below code snippet

 private Context context;
 private ArrayList<FoodModel> foodModelArrayList;

    public FoodAdapter(Context context, ArrayList<FoodModel> foodModelArrayList) {

        this.context = context;
        this.foodModelArrayList = foodModelArrayList;
    }
  • First line is creating object of context.
  • Second is creating an ArrayList with the objects of the FoodModel class.
  • This arraylist (foodModelArrayList) is our main data provider. We are filling this arraylist (foodModelArrayList) with data in Main Activity. We will see this in next step.
  • Then I have created one Constructor. We will get context and arraylist (foodModelArrayList) via the parameters of this constructor.

Look at the below lines

@Override
    public int getViewTypeCount() {
        return getCount();
    }
    @Override
    public int getItemViewType(int position) {

        return position;
    }

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

    @Override
    public Object getItem(int position) {
        return foodModelArrayList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }
  • Above are necessary method to be overriden. Mainly I have used that arraylist (foodModelArrayList)  in above methods.

Read the following code snippet

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_item, null, true);

            holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
            holder.tvQty = (TextView) convertView.findViewById(R.id.tvQty);

            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvProduct.setText(foodModelArrayList.get(position).getProduct());
        holder.tvQty.setText(String.valueOf(foodModelArrayList.get(position).getQty()));

        return convertView;
    }

Above getView() creates every row of the listview.

This method is called for the number of times which is equal to the number of rows of the listview.

For example, if listview have five rows then getView() method is called for five times.

In this getView() method, compiler will first inflate the row layout (list_item.xml) which we have already created in step 1.

Then we will fill the values of two textviews. (One is for product name and another is for quantity.)

We are using arraylist (foodModelArrayList) to fill the values in textviews.

Step 4. Making Final Changes

Now let us create our app’s main layout structure.

For this, you need to replace your existing code of activity_main.xml with the following one

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#000"
        android:textSize="30sp"
        android:textColor="@color/colorAccent"
        android:text="Zyka Restaurant"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#000"
        android:textSize="25dp"
        android:textColor="@color/colorAccent"
        android:text="Oeder Summary"
        />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#1ec2d4"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:textColor="#fff"
            android:textSize="20sp"
            android:gravity="center"
            android:layout_weight="2"
            android:text="Product Name"/>

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textColor="#fff"
            android:textSize="20sp"
            android:gravity="center"
            android:layout_weight="1"
            android:text="Quantity"/>

    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="20dp"
        android:id="@+id/listView">

    </ListView>


</LinearLayout>

In the above file, I have added three sticky or static headers. It means that these headers can not be changed or modified.

First two TextViews and LinearLayout with horizontal orientation represents these three sticky headers.

Just below these headers, I have added our multi column listview.

After this, write down the below code snippet in MainActivity.java file.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private String[] Products = new String[]{"Pizza", "Burger", "Pasta", "Salad", "Rice","Sandwich","Chips"};
    private int[] Qty = new int[]{3, 4, 2, 1, 5,8,20};
    private ArrayList<FoodModel> foodModelArrayList;
    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);

        //foodModelArrayList = new ArrayList<>();
        foodModelArrayList = populateList();

        FoodAdapter foodAdapter = new FoodAdapter(this,foodModelArrayList);
        listView.setAdapter(foodAdapter);

    }

    private ArrayList<FoodModel> populateList(){

        ArrayList<FoodModel> list = new ArrayList<>();

        for(int i = 0; i < 7; i++){
            FoodModel foodModel = new FoodModel();
            foodModel.setProduct(Products[i]);
            foodModel.setQty(Qty[i]);
            list.add(foodModel);
        }

        return list;
    }
}

What does above code means ?

See the below snippet

    private String[] Products = new String[]{"Pizza", "Burger", "Pasta", "Salad", "Rice","Sandwich","Chips"};
    private int[] Qty = new int[]{3, 4, 2, 1, 5,8,20};
    private ArrayList<FoodModel> foodModelArrayList;
    private ListView listView;

First line is creating one string array called Products. It includes the names of the various food products.

Similarly, second line is creating one integer array which holds the quantity of the food products.

Third and fourth lines are making objects ArrayList and Listview respectively.

Attend below lines

 foodModelArrayList = populateList();

 FoodAdapter foodAdapter = new FoodAdapter(this,foodModelArrayList);
 listView.setAdapter(foodAdapter);

Second line is creating a new object of FoodAdapter class and third one is setting this adapter object with the ListView.

First line will insert the data in foodModelArrayList using populateList() method.

populateList() method holds the below code

 private ArrayList<FoodModel> populateList(){

        ArrayList<FoodModel> list = new ArrayList<>();

        for(int i = 0; i < 7; i++){
            FoodModel foodModel = new FoodModel();
            foodModel.setProduct(Products[i]);
            foodModel.setQty(Qty[i]);
            list.add(foodModel);
        }

        return list;
    }
  • One for loop is there with six iterations.
  • In every iteration, compiler will create the object of the FoodModel class.

Then it will set the product name and quantity to this object. After this, the compiler adds this object in the arraylist.





4. Android ListView Section Header Example Group By Header

I will explain about android listview section header with example in this article.

You will make a listview with a specific header between it’s row items.

Last Views

After all of this hard work, you should get the listview like below video

What is Section Header ?

Section header means that a header among listview items to sort them with particular category.

For example, you are making listview with county names. Now countries can be categorized by continents.

So, you will make the first row as a section header which have continent name like Asia. Then in the second, third row you will write Asian countries like India, Chine etc.

After Asia, again you will make listview row as a section header and will name it Europe and further this process continues.

In this example, we will group listview by vehicle type as a header. We will create custom view for header which is different than the normal listview item.

So let us follow all the below steps carefully.

Step 1. Layout Resources

Different layout for header and normal rows requires different layout xml files. Hence, we will create two layout resources files.

Make two xml files res->layout directory.

Give a name lv_child.xml to first file and lv_header.xml to second xml file.

Add below code in lv_child.xml

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="20dp"
        android:background="#e9771f"
        android:textColor="#ffffff"
        android:textSize="40sp"
        android:id="@+id/tvChild"
        android:text="Hello"/>

</android.support.constraint.ConstraintLayout>

Above file will create the layout for normal listview row items.

Copy following code in lv_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="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="20dp"
        android:background="#e91fce"
        android:textColor="#13cac7"
        android:textSize="40sp"
        android:id="@+id/tvVehicle"
        android:text="Hello"/>

</LinearLayout>

This code will generate the layout for header rows.

Step 2. Separate Classes

We need to create two java classes for both normal and header rows.

This class will work as a general model class. So make two new java classes and name them HeaderModel.java and ChildModel.java respectively.

Write down the below code in HeaderModel.java

public class HeaderModel implements MainActivity.ListItem{

    String header;

    public void setheader(String header) {
        this.header = header;
    }

    @Override
    public boolean isHeader() {
        return true;
    }

    @Override
    public String getName() {
        return header;
    }
}

isHeader() method will tell compiler whether the listview row is a header row or a normal row.

setheader(String header) method will set the string as a header title which passes as a parameter.

getName() method will return a header text in a string format.

Now add the below code in ChildModel.java

public class ChildModel implements MainActivity.ListItem{

    String child;

    public void setChild(String child) {
        this.child = child;
    }

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

    @Override
    public String getName() {
        return child;
    }
}

Similar methods are used here as HeaderModel.java class. Only difference is that get method will return normal row item text and set method will set the text as a row item name.

Step 3. Custom Adapter

As always, every listview requires an adapter class which will provide the data to populate the listview rows.

Make a new java class named CustomeAdapter.java and add below code in it.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;

/**
 * Created by Parsania Hardik on 03-Jan-17.
 */
public class CustomeAdapter extends BaseAdapter {

    private Context context;
    private ArrayList<MainActivity.ListItem> listItemArrayList;

    public CustomeAdapter(Context context,ArrayList<MainActivity.ListItem> listItemArrayList) {

        this.context = context;
        this.listItemArrayList = listItemArrayList;
    }

    @Override
    public int getViewTypeCount() {
        return getCount();
    }
    @Override
    public int getItemViewType(int position) {

        return position;
    }

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

    @Override
    public Object getItem(int position) {
        return listItemArrayList.get(position);
    }

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

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            if(listItemArrayList.get(position).isHeader()){
                convertView = inflater.inflate(R.layout.lv_header, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvVehicle);
            }
            else {
                convertView = inflater.inflate(R.layout.lv_child, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvChild);
            }


            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvLabel.setText(listItemArrayList.get(position).getName());

        return convertView;
    }

    private class ViewHolder {

        protected TextView tvLabel;

    }

}

Here I am populating listview with the help of the listItemArraylist.

listItemArraylist is created in the Main activity and passed as a parameter to the constructor of this adapter class.

Hence, let us first understand the Main Activity so it would be easier to understand adapter class.

Step 4. Final Codes

Now the final task is to write Main Activity code.

Add below code in activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:id="@+id/listView"/>

</android.support.constraint.ConstraintLayout>

Write the following code in MainActivity.java file

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private ListView lv;
    private CustomeAdapter customeAdapter;

    private String[] vehicleTypes = new String[]{"Cars", "Bikes",
            "Air Crafts","Old Vehicles"};

    private ArrayList<ListItem> listItemArrayList;

    private String[] childnames = new String[]{"Range Rover", "Lamborghini",
            "Rolls Royce","Ferrari","Harley davidson","Ducati","BMW","Honda","Boeing","Airbus","Royal Air","Space X","Horse","Elephant","Camel","Donkey"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lv = (ListView) findViewById(R.id.listView);

        listItemArrayList = new ArrayList<>();
        populateList();

        customeAdapter = new CustomeAdapter(this,listItemArrayList);
        lv.setAdapter(customeAdapter);


    }

    public interface ListItem {
         boolean isHeader();
         String getName();
    }

    private void populateList(){

        int headerdone = 0, childdone = 0;

        for(int i = 0; i < 20; i++){

            if(i == 0 || i == 5 | i == 10 | i == 15){
                HeaderModel vehicleModel = new HeaderModel();
                vehicleModel.setheader(vehicleTypes[headerdone]);
                listItemArrayList.add(vehicleModel);
                headerdone = headerdone + 1;
            }else {
                ChildModel childModel = new ChildModel();
                childModel.setChild(childnames[childdone]);
                listItemArrayList.add(childModel);
                childdone = childdone + 1;
            }
        }

    }
}

Diving Deep In Main Activity

Consider below code

  private String[] vehicleTypes = new String[]{"Cars", "Bikes",
            "Air Crafts","Old Vehicles"};
 private String[] childnames = new String[]{"Range Rover", "Lamborghini",
            "Rolls Royce","Ferrari","Harley davidson","Ducati","BMW","Honda","Boeing","Airbus","Royal Air","Space X","Horse","Elephant","Camel","Donkey"};

First string array vehicleTypes will provide the header names.

Secong string array childnames will provide the text names for normal listview rows.

listItemArraylist is defined as below

 private ArrayList<ListItem> listItemArrayList;

Here ListItem is an Interface which in implemented by both model classes :  HeaderModel.java and ChildModel.java.

So the methods present is the ListItem interface are overriden in HeaderModel.java and ChildModel.java.

Following is the code for the Interface ListItem

public interface ListItem {
         boolean isHeader();
         String getName();
    }

As we have seen earlier in Step 2 that both the methods of Interface ListItem are overriden in HeaderModel.java and ChildModel.java.

Now we will create data structure using populateList() method.

Below is the source code for the populateList() method.

 private void populateList(){

        int headerdone = 0, childdone = 0;

        for(int i = 0; i < 20; i++){

            if(i == 0 || i == 5 | i == 10 | i == 15){
                HeaderModel vehicleModel = new HeaderModel();
                vehicleModel.setheader(vehicleTypes[headerdone]);
                listItemArrayList.add(vehicleModel);
                headerdone = headerdone + 1;
            }else {
                ChildModel childModel = new ChildModel();
                childModel.setChild(childnames[childdone]);
                listItemArrayList.add(childModel);
                childdone = childdone + 1;
            }
        }

    }

Here I have make four child rows for each header.

Listview will start with the first header then four rows will be it’s child items. The fifth row will be second header and then four rows will be the child row for second header and it continues in this fashion.

So the first, sixth, eleventh and sixteenth row of the listview will be header rows.

Hence in the for loop if condition will be true for four times.

When the if condition is true, object of the HeaderModel will be added to the listIemArrayList

In all other for loop iterations, object of the ChildModel will be added to the listIemArrayList.

After completion of for loop, listIemArrayList is passed to the adapter constructor.

Now let’s see the adapter code.

Understanding Adapter

Read the below code for getView() method

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

        if (convertView == null) {
            holder = new ViewHolder();
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            if(listItemArrayList.get(position).isHeader()){
                convertView = inflater.inflate(R.layout.lv_header, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvVehicle);
            }
            else {
                convertView = inflater.inflate(R.layout.lv_child, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvChild);
            }


            convertView.setTag(holder);
        }else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)convertView.getTag();
        }

        holder.tvLabel.setText(listItemArrayList.get(position).getName());

        return convertView;
    }

This method will fetch the data from listIemArrayList and will populate the listview using that data.

Look at the following codes

 if(listItemArrayList.get(position).isHeader()){
                convertView = inflater.inflate(R.layout.lv_header, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvVehicle);
            }
            else {
                convertView = inflater.inflate(R.layout.lv_child, null, true);
                holder.tvLabel = (TextView) convertView.findViewById(R.id.tvChild);
            }

It will check for the header object by using isHeader() method.

If isHeader() method returns true then compiler will inflate the lv_header.xml file as a listview row layout.

Otherwise it will inflate the lv_child.xml

After checking for above conditions and inflating the layout file compiler will set the text pf the listview row using the below line

 holder.tvLabel.setText(listItemArrayList.get(position).getName());